<?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: Luca</title>
    <description>The latest articles on DEV Community by Luca (@lucabro).</description>
    <link>https://dev.to/lucabro</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%2F3862208%2Fe383f9bb-bfc7-478d-a4a2-d565380e739d.png</url>
      <title>DEV Community: Luca</title>
      <link>https://dev.to/lucabro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucabro"/>
    <language>en</language>
    <item>
      <title>Where should related code live? A structured look at an unresolved debate</title>
      <dc:creator>Luca</dc:creator>
      <pubDate>Sat, 25 Apr 2026 21:44:05 +0000</pubDate>
      <link>https://dev.to/lucabro/where-should-related-code-live-a-structured-look-at-an-unresolved-debate-4b44</link>
      <guid>https://dev.to/lucabro/where-should-related-code-live-a-structured-look-at-an-unresolved-debate-4b44</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;This article does not take sides. The intention is to map a debate that has been running through frontend development for years without reaching a conclusion, because it probably cannot reach one: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where should related code live, and who gets to decide?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The question touches file structure, framework philosophy, compiler design, and — underneath all of it — what we think the unit of work in UI development actually is. We will look at how different frameworks have answered it, what tradeoffs each answer carries, and what changes when a compiler sits between the developer and the runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Thesis&lt;/li&gt;
&lt;li&gt;Historical context&lt;/li&gt;
&lt;li&gt;
Evidence and positions

&lt;ol&gt;
&lt;li&gt;Vue and the Single File Component&lt;/li&gt;
&lt;li&gt;React and the absence of a position&lt;/li&gt;
&lt;li&gt;A critical voice from within Vue&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

Analysis

&lt;ol&gt;
&lt;li&gt;The implicit constraint&lt;/li&gt;
&lt;li&gt;The paradox of the compile step&lt;/li&gt;
&lt;li&gt;What the debate is actually about&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

A concrete case: Origami

&lt;ol&gt;
&lt;li&gt;The file as a feature boundary&lt;/li&gt;
&lt;li&gt;Styling as a language-level concern&lt;/li&gt;
&lt;li&gt;What the compiler changes, and what it does not&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

Open questions

&lt;ol&gt;
&lt;li&gt;Is the component the right unit of work?&lt;/li&gt;
&lt;li&gt;Where should discipline be enforced?&lt;/li&gt;
&lt;li&gt;What does the developer actually need to know?&lt;/li&gt;
&lt;li&gt;Is the output the right place to look?&lt;/li&gt;
&lt;li&gt;What happens when the target changes?&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Thesis
&lt;/h2&gt;

&lt;p&gt;Every frontend project makes a structural decision that is rarely made explicitly: what is the right unit of colocation?&lt;/p&gt;

&lt;p&gt;Colocation, in the broad sense, means keeping things that belong together close to each other in the codebase. The harder question is what "belonging together" means. Does it mean same technology — all the CSS in one place, all the JavaScript in another? Does it mean same component — template, logic, and styles of a single UI unit in one file? Does it mean same feature — all the components that compose a product boundary, regardless of how many they are, grouped together?&lt;/p&gt;

&lt;p&gt;Each of these answers has been the dominant convention at some point in the history of frontend development. None of them has ever been universally correct and each carries assumptions about how developers work, how codebases scale, and what the framework owes the developer versus what the developer owes the framework.&lt;br&gt;
As stated before, this article does not resolve the question, but tries to make it more precise.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Historical context
&lt;/h2&gt;

&lt;p&gt;The separation of HTML, CSS, and JavaScript into distinct files, or at least into well-delimited areas within a single HTML file, was not a design philosophy. It was a practical response to the constraints of the early commercial web: the period, roughly from the mid-1990s onward, when writing code for browsers became a professional activity rather than an academic one.&lt;/p&gt;

&lt;p&gt;In that context, the separation made sense, within limits. Pages were primarily documents with interactivity layered on top, and as long as that interactivity remained marginal — form validation, simple animations, occasional DOM manipulation — the three layers could be treated as largely independent. You could change the stylesheet without touching the markup, and the scripts would survive. The file boundary reflected something that was at least approximately true about the conceptual boundary.&lt;/p&gt;

&lt;p&gt;That approximation eroded quickly. As interactivity grew more ambitious — and particularly as libraries like jQuery made deep DOM manipulation the norm rather than the exception — the independence assumption became harder to sustain. JavaScript selected elements by class and ID, which meant the markup could not change without breaking the scripts, CSS began depending on classes toggled dynamically by JavaScript and the markup itself started being generated and modified at runtime. &lt;/p&gt;

&lt;p&gt;The three layers continued to live in separate files, but they were no longer independent: changing one almost always meant changing the others. The physical boundary between files remained; the conceptual boundary that had justified it dissolved.&lt;/p&gt;

&lt;p&gt;The convention outlasted the reasoning. By the time the component model became dominant (through the successive iterations of the major frontend frameworks like Angular, React, and Vue) the separation of file types was already more habit than principle. The frameworks that emerged in this period made different choices about whether to preserve it, challenge it, or ignore it entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Evidence and positions
&lt;/h2&gt;

&lt;p&gt;The three positions that are worth examining in detail, represent meaningfully different answers to the same question and are grounded in documented reasoning rather than convention alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Vue and the Single File Component
&lt;/h3&gt;

&lt;p&gt;Vue's position is the most explicitly argued. The SFC format — one &lt;code&gt;.vue&lt;/code&gt; file containing a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, and a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block — it is presented as a philosophical stance, and the &lt;a href="https://vuejs.org/guide/scaling-up/sfc.html#what-about-separation-of-concerns" rel="noopener noreferrer"&gt;documentation states it directly&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Separation of concerns is not equal to the separation of file types. The ultimate goal of engineering principles is to improve the maintainability of codebases. Separation of concerns, when applied dogmatically as separation of file types, does not help us reach that goal in the context of increasingly complex frontend applications."&lt;br&gt;
&lt;em&gt;— Vue.js official documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The argument is precise: the old separation was a proxy for maintainability, not maintainability itself. Inside a component, template, logic, and styles are inherently coupled — colocating them makes the component more cohesive, not less disciplined. The SFC is Vue's answer to what the right unit of colocation is: the component, with all its concerns, in one place.&lt;/p&gt;

&lt;p&gt;This is a coherent position, and it was a meaningful intervention at the time it was made. It gave developers a clear convention, tooling a clear target, and the ecosystem a shared vocabulary. It also introduced a constraint that was never fully declared as such: one file, one component. The format enforces it structurally — one &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; block, one &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block — without ever stating it as a rule.&lt;/p&gt;

&lt;p&gt;It is also worth noting that Vue itself has iterated on this model over time. The introduction of the Composition API in Vue 3 was in part a response to limitations that emerged from the Options API's structure: logic related to the same concern was scattered across different option blocks — data, computed, methods, watch — making large components harder to navigate. The Composition API allowed grouping by logical concern rather than by option type. The unit of colocation shifted, slightly, even within the SFC model itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 React and the absence of a position
&lt;/h3&gt;

&lt;p&gt;React's stance on file organization is, by design, not a stance. The framework has never prescribed how components should be distributed across files, how folders should be structured, or what the relationship between a file and a component should be. This absence is deliberate and has been articulated by members of the React core team over the years.&lt;/p&gt;

&lt;p&gt;Dan Abramov's oft-cited advice on project structure — &lt;a href="https://react-file-structure.surge.sh/" rel="noopener noreferrer"&gt;"move files around until they feel right"&lt;/a&gt; &lt;em&gt;(original tweet no longer available)&lt;/em&gt; — is sometimes read as flippant, but it reflects a genuine philosophical choice: the framework should not impose organizational opinions that belong to the team and the project. A single &lt;code&gt;.tsx&lt;/code&gt; file can export one component or twenty. Nothing in React prevents either, nothing in the ecosystem has ever standardized around one approach.&lt;/p&gt;

&lt;p&gt;Abramov has also been more precise about the underlying principle, in fact, as reported by Kent C. Dodds, &lt;a href="https://kentcdodds.com/blog/colocation#:~:text=Things%20that%20change%20together%20should%20be%20located%20as%20close%20as%20reasonable." rel="noopener noreferrer"&gt;who wrote one of the more thorough treatments of the subject&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Things that change together should be located as close as reasonable."&lt;br&gt;
&lt;em&gt;— Dan Abramov, as cited in Colocation by Kent C. Dodds&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a principle, not a rule. It shifts the question from "&lt;em&gt;how should files be organized"&lt;/em&gt; to &lt;em&gt;"what changes together in this codebase"&lt;/em&gt; — and leaves the answer to the developer. The cost of this approach is visible in practice: React codebases vary enormously in structure, and the variance is not always a sign of flexibility. Sometimes it is a sign that the team never made the decision explicitly and accumulated the consequences of not making it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 A critical voice from within Vue
&lt;/h3&gt;

&lt;p&gt;The most instructive position may be the one that emerged from inside the Vue ecosystem itself. In 2019, Markus Oberlehner, Vue developer and writer, published an article titled &lt;a href="https://markus.oberlehner.net/blog/separation-of-concerns-re-revisited" rel="noopener noreferrer"&gt;&lt;em&gt;"Separation of Concerns Re-Revisited"&lt;/em&gt;&lt;/a&gt; that challenged the colocation-by-component model from a direction that the framework's own documentation did not address.&lt;/p&gt;

&lt;p&gt;His argument was not that SFCs were wrong, but that the model had a blind spot: logic reuse. If two components share the same reactive logic but render different things, the SFC model — where logic lives inside the component — offers no clean path. The concern is real and cannot be colocated with both components simultaneously. You extract it, it loses its home, and the colocation principle breaks down at exactly the moment it is most needed.&lt;br&gt;
&lt;a href="https://markus.oberlehner.net/blog/separation-of-concerns-re-revisited#:~:text=And%20here%20the%20new%20paradigm%20of%20separating%20concerns%20not%20by%20file%20type%20but%20by%20logical%20units%20has%20led%20us%20astray." rel="noopener noreferrer"&gt;As Oberlehner wrote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here the new paradigm of separating concerns not by file type but by logical units has led us astray."&lt;br&gt;
&lt;em&gt;— Separation of Concerns Re-Revisited, Markus Oberlehner, 2019&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This critique arrived before the Composition API, and it is one of the pressures that made the Composition API necessary. Composables, reactive logic extracted into standalone functions, importable across components, are Vue's answer to the problem Oberlehner identified. They work, but they also mean that not all logic lives in the SFC anymore, and the clean "everything in one place" model becomes a partial truth rather than a complete one.&lt;br&gt;
None of this invalidates the SFC. It shows that the model has edges, and that the framework's own evolution has been shaped by the pressure of those edges.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Analysis
&lt;/h2&gt;

&lt;p&gt;The three positions described above are not simply different preferences. They encode different assumptions about what the framework owes the developer, what the developer owes the codebase, and where the boundary between the two should sit. Putting them in tension reveals something that none of them states explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 The implicit constraint
&lt;/h3&gt;

&lt;p&gt;Vue's SFC model introduces a constraint that is never declared as a design principle: one file, one component. It is a structural consequence of the format — the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; block is singular, the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block is singular — but it is never presented as a rule the developer must follow. it simply follows from the shape of the file.&lt;/p&gt;

&lt;p&gt;In practice, this means that the unit of authoring and the unit of the component are forced to coincide. When a developer is working on a feature — something with a natural boundary in the product, but composed of several related components — those components must live in separate files. The feature, as a concept, has no representation in the file structure unless the developer imposes one through folder organization. The file boundary is drawn by the format, not by the problem.&lt;/p&gt;

&lt;p&gt;This is not inherently wrong. Consistent boundaries have value: they make codebases predictable, tooling easier to write, and onboarding faster. The question is whether the boundary drawn by the format is always the right one, or whether it is one reasonable choice that has been made invisible by its own consistency.&lt;/p&gt;

&lt;p&gt;React's absence of constraint makes the same question visible from the other side: without a format that enforces anything, the boundary must be chosen explicitly and that choice is frequently not made. The result is not freedom so much as deferred decision. Codebases end up structured by inertia, by the habits individual developers brought from previous projects, by the path of least resistance at the moment a new file was created. The principle — things that change together should live close together — is sound, but a principle without a mechanism is advice, and advice is easy to ignore under deadline pressure.&lt;/p&gt;

&lt;p&gt;Both approaches, then, carry a cost, Vue's cost is rigidity at the authoring level: the format decides the boundary. React's cost is inconsistency at the team level: the team must decide the boundary, and frequently doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 The paradox of the compile step
&lt;/h3&gt;

&lt;p&gt;There is a detail that tends to disappear from discussions about colocation, and it is worth making explicit: regardless of where components live at the authoring level, what arrives at the runtime is structurally similar across all modern frameworks.&lt;/p&gt;

&lt;p&gt;Vue compiles SFCs into JavaScript modules and CSS. React compiles JSX into JavaScript. Svelte compiles its own format into JavaScript and CSS. The output in all cases is a collection of assets that, in their general shape, resembles what the early commercial web produced by hand: markup logic encoded in JavaScript, styles in CSS, structure determined by the DOM. The difference is that the output is not written for human reading. It is optimized for the runtime, minified for the network, and largely opaque to anyone who opens it in a text editor.&lt;/p&gt;

&lt;p&gt;This matters for the colocation discussion because it means the separation that component-based development sought to overcome at the authoring level is reproduced at the output level not as a choice, but as a consequence of how browsers work. The framework abstracts the authoring experience; it cannot abstract the runtime target. Every &lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt; block in a Vue SFC becomes, after compilation, a stylesheet with generated attribute selectors applied to every rule. The developer writes colocation; the browser receives separation.&lt;/p&gt;

&lt;p&gt;Vue's scoped styles are a concrete example of this indirection. When a developer writes a &lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt; block, Vue generates a unique attribute — something like data-v-xxxxxxxx&lt;sup id="fnref1"&gt;1&lt;/sup&gt; — adds it to every element in the template, and mirrors it in every CSS selector in the style block. The result is style encapsulation that works reliably (well... usually), but the mechanism is invisible to the developer and introduces a layer of transformation between what is written and what is shipped.&lt;/p&gt;

&lt;p&gt;This is not a criticism of the approach. It is a description of what the approach actually is: a compiler convention that manages complexity on behalf of the developer. The developer does not need to know about data-v-xxxxxxxx. The question is whether not knowing is always preferable to knowing, a question we will return to in section 6.3.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 What the debate is actually about
&lt;/h3&gt;

&lt;p&gt;Taken together, the positions and their tensions suggest that the colocation debate is not primarily about file structure. File structure is the surface. Underneath it, the debate is about three things that are rarely separated cleanly.&lt;/p&gt;

&lt;p&gt;The first is the unit of work. What is the thing a developer is working on at any given moment — a component, a feature, a concern, a module? Different answers produce different organizational instincts&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, and frameworks that encode one answer make the others harder to express.&lt;br&gt;
The second is the locus of discipline. Every organizational approach requires discipline to work at scale. The question is where that discipline is enforced: by the format, by the framework, by the team's conventions, or by the developer's judgment. Enforcing it at the format level&lt;sup id="fnref3"&gt;3&lt;/sup&gt; makes it consistent and invisible; enforcing it at the team level makes it flexible and fragile; leaving it to individual judgment makes it variable and honest&lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The third is the relationship between authoring and output. What the developer writes and what the runtime receives are not the same thing, and the distance between them is managed by the compiler. That distance can be small and transparent, or large and opaque. Neither extreme is obviously better — transparency has costs in verbosity and cognitive load, opacity has costs in debuggability and trust — but the choice of where to put it shapes everything else.&lt;/p&gt;

&lt;p&gt;None of these three questions has a universal answer. They have answers that are appropriate for a given project scale, team structure, and product complexity. What the frameworks have done, each in its own way, is make one set of answers the default — and defaults, once established, tend to look like facts.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. A concrete case: Origami
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/lucabro81/origami" rel="noopener noreferrer"&gt;Origami&lt;/a&gt; is a full-stack framework with a closed-vocabulary DSL and a compiler written in Rust. Its authoring format is the &lt;code&gt;.ori&lt;/code&gt; file. It currently compiles to Vue SFCs, which means Vue is the runtime target — but it is a temporary one, chosen for its runtime characteristics, not because the language it exposes to the developer is Vue or is constrained by Vue's conventions.&lt;/p&gt;

&lt;p&gt;This distinction matters for the colocation discussion because it means that decisions made at the authoring level — how components are organized, how styles are expressed, what a file represents — are not bound by what the compilation target expects. The compiler mediates between the two: what Vue receives is what Vue expects; what the developer writes is what the problem requires.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 The file as a feature boundary
&lt;/h3&gt;

&lt;p&gt;In Origami, a &lt;code&gt;.ori&lt;/code&gt; file can contain multiple component definitions. The compiler reads each definition, understands its boundaries, and generates one Vue SFC per component. Vue receives a set of files that conform exactly to its format. The fact that several of those components were authored together, in the same file, because they belong to the same feature or share a conceptual boundary, is information that exists at the authoring level and is resolved at compile time.&lt;/p&gt;

&lt;p&gt;This is the concrete consequence of having a compiler between the developer and the runtime: the unit of authoring and the unit of the compilation target no longer need to coincide. In Vue, they must — the format enforces it. In Origami, they can diverge, because the format is not the SFC, the SFC is the output.&lt;/p&gt;

&lt;p&gt;What this does not resolve — and it is worth being explicit about this — is the discipline problem. A compiler that permits colocation does not prescribe when colocation is appropriate. A &lt;code&gt;.ori&lt;/code&gt; file containing thirty components compiles without complaint. The freedom is real, and so is the responsibility it transfers to the developer and the team. The framework can enforce vocabulary and token compliance at compile time; it cannot enforce architectural judgment. That remains a human problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Styling as a language-level concern
&lt;/h3&gt;

&lt;p&gt;The way Origami handles styling makes the authoring-to-output distance visible in a way that is worth examining separately.&lt;/p&gt;

&lt;p&gt;In a &lt;code&gt;.ori&lt;/code&gt; file, styling is not a block appended to the component definition. It is integrated at the DSL level: every style value must exist in the token dictionary, and if it does not, the build fails. &lt;/p&gt;

&lt;p&gt;The type system and the design system are the same thing. A developer cannot write an arbitrary color value, a spacing value that does not exist in the system, or a typography setting that has not been defined. The compiler enforces compliance, not by convention or linting, but by making non-compliant values grammatically invalid.&lt;/p&gt;

&lt;p&gt;This is a different relationship between the developer and the styling layer than the one Vue's scoped styles establish. In Vue, the developer writes CSS — or something very close to it — inside a &lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt; block, and the compiler handles encapsulation through generated attribute selectors. The mechanism is invisible and reliable. The developer is not required to know how it works, and in most cases, not knowing is the right default.&lt;/p&gt;

&lt;p&gt;In Origami, the mechanism is not hidden. The compiler's behavior with respect to styles is a consequence of explicit rules in the grammar, not a convention layered on top of a general-purpose language. Whether this is an advantage depends on what the developer values and what the project requires. For a team working within a strict design system, where token compliance is a hard requirement rather than a guideline, the compile-time enforcement removes an entire category of review feedback and runtime divergence. For a team that needs flexibility in the styling layer, it is a constraint that may not be appropriate.&lt;/p&gt;

&lt;p&gt;What both approaches share — and this connects back to the paradox described in the previous section — is that the output, after compilation, reproduces the structure of the old web. Origami generates Vue SFCs; Vue compiles those SFCs into JavaScript and CSS; the browser receives assets that are not meaningfully different in structure from what a developer in the late 1990s would have produced by hand, only optimized beyond legibility. The colocation that exists at the authoring level, across both frameworks, does not survive to the runtime. It was never meant to. It exists to serve the developer, and it ends where the compiler begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 What the compiler changes, and what it does not
&lt;/h3&gt;

&lt;p&gt;The general principle that Origami illustrates is this: when a compiler sits between the developer and the runtime, architectural constraints that would otherwise be enforced by the format can be moved to the level of convention&lt;sup id="fnref5"&gt;5&lt;/sup&gt;. The SFC format enforces one-component-per-file because the format has no mechanism for anything else&lt;sup id="fnref6"&gt;6&lt;/sup&gt;. A compiler that targets SFCs but accepts a different authoring format can make different choices, because the format is no longer the contract, the compiler is.&lt;br&gt;
This is not unique to Origami. Any sufficiently expressive compilation layer could make the same move. What Origami makes concrete is that the move is available, that it does not require changes to the runtime, and that the runtime remains entirely unaware that it happened.&lt;/p&gt;

&lt;p&gt;What the compiler does not change is the nature of the tradeoffs. Colocation at the feature level is more expressive and more flexible than colocation, vertically, at the component level, but it requires more discipline to keep coherent at scale. Styling integrated into the DSL is more enforceable and more consistent than styling in a general-purpose block, but it is also less flexible and more opinionated. These are not problems the compiler solves. They are choices the compiler makes available, and then leaves to the people using it.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Open questions
&lt;/h2&gt;

&lt;p&gt;This article began with a question about where related code should live and who gets to decide. It has not answered that question, because the question does not have an answer that is independent of context. What it has, hopefully, is more precise edges than it did at the start.&lt;/p&gt;

&lt;p&gt;What follows is not a conclusion but a set of questions that the analysis leaves open, questions that any team or framework designer working in this space will eventually have to confront, explicitly or by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Is the component the right unit of work?
&lt;/h3&gt;

&lt;p&gt;The component model has been so dominant for so long that it has become difficult to see it as a choice. But it is one. A component is a useful unit for reasoning about UI behavior and encapsulation; it is less obviously useful as the primary unit of authoring organization.&lt;/p&gt;

&lt;p&gt;Features, in practice, do not map cleanly onto components. A feature is a product boundary — something a user can do, something the product offers — and it is almost always composed of multiple components with shared state, shared styling decisions, and shared conceptual context. When the authoring unit is the component, the feature has no natural home. It lives in a folder, if the team is disciplined, or it lives nowhere in particular, if the team is not.&lt;/p&gt;

&lt;p&gt;The question is whether this mismatch between the unit of authoring and the unit of product thinking is a problem to be solved, a tradeoff to be managed, or an irreducible consequence of how component-based development works. Different frameworks have given implicitly different answers. None has addressed the question directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Where should discipline be enforced?
&lt;/h3&gt;

&lt;p&gt;Every organizational approach requires discipline to remain coherent as a codebase scales. The frameworks examined here place that discipline in different locations: Vue places it in the format, React leaves it to the team, Origami places some of it in the compiler and the rest in the team's conventions.&lt;/p&gt;

&lt;p&gt;Each placement has consequences. Format-level discipline is consistent and invisible: it works without requiring the team to think about it, and it works the same way regardless of who is writing the code. Its cost is that it cannot adapt to cases the format did not anticipate. Team-level discipline is flexible and fragile — it can adapt to anything, but it depends on the team maintaining it under pressure, across time, through turnover. &lt;/p&gt;

&lt;p&gt;Compiler-level discipline is precise and enforceable for the things it covers, and silent about everything else.&lt;br&gt;
The open question is not which placement is correct. It is whether any single placement is sufficient, or whether coherent codebases at scale require discipline enforced at multiple levels simultaneously — and if so, how those levels should be designed to reinforce rather than contradict each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 What does the developer actually need to know?
&lt;/h3&gt;

&lt;p&gt;Vue's scoped styles work by generating attribute selectors the developer never writes and rarely inspects. The mechanism is invisible by design. React's styling solutions vary enormously — CSS modules, styled components, utility classes, inline styles — each with its own relationship between what is written and what is shipped. Origami's token enforcement makes the compiler's behavior explicit in the grammar rather than in the output.&lt;/p&gt;

&lt;p&gt;These are different answers to a question that is rarely asked directly: how much should the developer know about what the compiler does with their code?&lt;/p&gt;

&lt;p&gt;Transparency has costs: it requires the developer to carry more mental context, to understand mechanisms that could otherwise be abstracted away, to make decisions that the framework could make on their behalf. Opacity has different costs: it makes debugging harder when the abstraction leaks, it creates distance between intent and output that becomes visible at the worst moments, and it requires trust in the framework's judgment that may or may not be warranted.&lt;/p&gt;

&lt;p&gt;Neither extreme is obviously correct. The right balance depends on the team's expertise, the project's constraints, and the degree to which the framework's judgment can be trusted to align with the project's requirements. What is worth acknowledging is that this is a balance — a point on a spectrum that has been chosen, not a natural state of affairs.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 Is the output the right place to look?
&lt;/h3&gt;

&lt;p&gt;The analysis in section 4.2 noted that all modern frameworks, regardless of their authoring conventions, produce output that structurally resembles the early commercial web: JavaScript for behavior, CSS for presentation, HTML structure encoded in the DOM. The colocation that exists at the authoring level does not survive compilation. It was designed not to.&lt;/p&gt;

&lt;p&gt;This raises a question that is easy to overlook: if the output is always separation, and the authoring experience is always some form of colocation, then the debate about colocation is entirely a debate about developer experience — about what it is like to write and navigate a codebase, not about what the runtime receives or how it performs.&lt;/p&gt;

&lt;p&gt;That is not a dismissal of the debate. Developer experience has real consequences: it affects how quickly bugs are found, how confidently changes are made, how successfully a codebase is understood by someone new to it. But it does mean that arguments about colocation cannot be settled by looking at the output. The output is the same. The question is entirely about the humans on the other side of the compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.5 What happens when the target changes?
&lt;/h3&gt;

&lt;p&gt;This question is specific to architectures like Origami's, where the compilation target is explicit and, in principle, replaceable. If the authoring format is decoupled from the runtime target — if the &lt;code&gt;.ori&lt;/code&gt; file is not a Vue file that happens to have a different extension, but a genuinely independent language that the compiler translates to Vue — then changing the target should not require changing the source.&lt;/p&gt;

&lt;p&gt;In practice, this is an aspiration more than a guarantee. Compilation targets impose constraints that propagate upward: the semantics of the target language, the component model it expects, the styling primitives it supports. A compiler that targets Vue and a compiler that targets a WASM runtime are not interchangeable back-ends for the same front-end language without careful design. The question of how much the authoring layer can genuinely be insulated from the target is open, and the answer will depend on decisions that have not yet been made.&lt;/p&gt;

&lt;p&gt;What the question points toward, however, is worth holding onto: if the unit of authoring, the unit of compilation, and the unit of runtime execution are three distinct things — and if a compiler can manage the translation between them — then many of the constraints that currently feel architectural may turn out to be conventional after all. Whether that possibility is realized depends on the quality of the abstraction, and abstractions are only as good as the clarity of the thinking behind them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: this article was written with the assistance of AI as a collaborative drafting tool. The ideas, decisions, and experiences described are mine, and everything generated was carefully reviewed and revised by me.&lt;/em&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Vue's scoped style encapsulation works by generating a unique attribute — typically &lt;code&gt;data-v-xxxxxxxx&lt;/code&gt;, where the suffix is a hash derived from the component — and adding it to every element in the template and every selector in the style block. The result is CSS that only applies within the component's own DOM tree. The mechanism is reliable; it is also entirely invisible at the authoring level. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;strong&gt;"Organizational instincts"&lt;/strong&gt; refers to the implicit structural choices developers make when creating or moving files — where to put a new component, when to split a file, what a folder should represent. These choices are rarely made explicitly; they accumulate into the structure of the codebase over time. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;strong&gt;"Format level"&lt;/strong&gt; means the constraint is embedded in the grammar of the file format itself, not in a rule or convention. A &lt;code&gt;.vue&lt;/code&gt; file has a single &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; block by specification — there is no syntax for defining more than one. You cannot work around it because the parser does not recognize anything else. This is distinct from compiler-level enforcement, where the constraint is a deliberate rule written into the compiler's logic — real and enforceable, but also writable, modifiable, and extensible by whoever controls the compiler. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;strong&gt;"Variable and honest"&lt;/strong&gt; is not a compliment. It means that code organized by individual judgment reflects, accurately and without filter, the priorities and habits of whoever wrote it — which in a team context produces inconsistency that compounds over time. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;When a compiler accepts an authoring format that is independent of its output format, the constraints of the output format no longer apply to the author. A &lt;code&gt;.vue&lt;/code&gt; file cannot contain more than one component because the format has no syntax for it. A &lt;code&gt;.ori&lt;/code&gt; file can, because &lt;code&gt;.ori&lt;/code&gt; is not &lt;code&gt;.vue&lt;/code&gt; — the compiler handles the translation. What was an architectural limit of the format becomes a convention the team can choose to follow or not. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;The grammar of a &lt;code&gt;.vue&lt;/code&gt; file does not support the syntax for defining multiple components. It isn't a rule that someone wrote which you could potentially bypass, it is simply absent from the format specification. There is no &lt;code&gt;&amp;lt;component name="Foo"&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;component name="Bar"&amp;gt;&lt;/code&gt; within the same &lt;code&gt;.vue&lt;/code&gt; file. The structure doesn't exist, so the behavior is not possible. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>frontend</category>
      <category>react</category>
      <category>vue</category>
      <category>colocation</category>
    </item>
    <item>
      <title>Socratic AI: how I learned formal grammars (and built a compiler) without losing control of what I was building</title>
      <dc:creator>Luca</dc:creator>
      <pubDate>Sun, 05 Apr 2026 20:07:09 +0000</pubDate>
      <link>https://dev.to/lucabro/socratic-ai-how-i-learned-formal-grammars-and-built-a-compiler-without-losing-control-of-what-i-41hj</link>
      <guid>https://dev.to/lucabro/socratic-ai-how-i-learned-formal-grammars-and-built-a-compiler-without-losing-control-of-what-i-41hj</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The context&lt;/li&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;The method&lt;/li&gt;
&lt;li&gt;How it works in practice&lt;/li&gt;
&lt;li&gt;When the method isn't needed&lt;/li&gt;
&lt;li&gt;The limits of the method&lt;/li&gt;
&lt;li&gt;One way among many&lt;/li&gt;
&lt;li&gt;Links&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The context
&lt;/h2&gt;

&lt;p&gt;About a month ago I started building &lt;a href="https://github.com/lucabro81/clutter" rel="noopener noreferrer"&gt;Clutter&lt;/a&gt;: a compiler for a custom markup language that outputs Vue SFCs. The idea was to enforce design system compliance at compile time, if a value isn't in the token dictionary, the build fails. It stayed a POC, proved its point, and opened larger questions that are evolving into a different project.&lt;br&gt;
I wrote it in Rust, which I'm learning. I had no background in formal grammars beyond a university course I took over twenty years ago and mostly forgot.&lt;/p&gt;

&lt;p&gt;The goal was to have a working compiler I fully understood, every design choice and the reasoning behind it. To know how it worked well enough to make deliberate decisions about it.&lt;br&gt;
That's a different problem from "I need a lexer and a parser." It's closer to: I need to know what I'm choosing, and why, before I write a single line of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The problem
&lt;/h2&gt;

&lt;p&gt;There are two ways to use AI when you're building something you don't fully master yet, something with undefined edges, or parts you don't entirely understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first:&lt;/strong&gt; you delegate. You describe what you need, as clearly as you can given your knowledge at that moment, and your best idea of how to approach the problem. It works, and it's one of the concrete possibilities this era has brought us. But it has a limit that becomes more visible as the scope grows and touches more layers of the process: it gets hard to keep track, to debug, to make others part of the decisions made. In the end it's not ownership anymore — it's just possession.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second:&lt;/strong&gt; you do it yourself. The old way. You sit in front of your IDE and write code one line at a time. This requires deep knowledge of the domain and the tools — if that knowledge isn't there, it needs to be built first, otherwise the quality of what you produce suffers. Ownership exists and is total, but it's incredibly slow, and this is no longer that time.&lt;/p&gt;

&lt;p&gt;There is a third way, and that's what this article is about.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. The method
&lt;/h2&gt;

&lt;p&gt;The Socratic method, or maieutics, is a philosophical method of inquiry based on dialogue. Socrates didn't teach by transferring knowledge, he asked questions, one after another, until the other person arrived at understanding on their own. The underlying idea is that knowledge can't be transferred, it has to be drawn out, in some sense it has to emerge spontaneously.&lt;/p&gt;

&lt;p&gt;Applied to software development, the principle is the same. Instead of asking AI to write a solution, you ask it to ask you the right questions. Not "write me a lexer", but "I need to understand what a lexer does in the context of my specific compiler, keep asking me questions until I can answer them myself." The code comes later, and it comes from understanding, not in place of it.&lt;br&gt;
This connects naturally to the practice of breaking a complex problem into simpler ones. The difference is that here the decomposition doesn't happen upfront, it emerges from the dialogue. Every question the model asks forces you to make explicit what you know and what you don't, and the gaps become visible before they become bugs.&lt;/p&gt;

&lt;p&gt;After Clutter, working on its evolution, I decided to rewrite the compiler from scratch starting from the formal grammar, the ownership problem we talked about was too present to ignore. That's where I ran the first real Socratic session. It didn't come from urgency, from needing to recover ground on something that wasn't working. It was a deliberate choice to understand before building, knowing that what I was about to tackle was larger, and that I couldn't afford opaque parts in the middle. Everything had to be known to me, no shortcuts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One clarification before moving on:&lt;/strong&gt; this approach works when you already have an idea, even a rough one, of what you want to build. AI is not an oracle, it doesn't know where you're going better than you do. It's an interlocutor that helps you make your idea explicit and test its solidity. Every decision remains yours. We'll come back to this. &lt;/p&gt;
&lt;h2&gt;
  
  
  4. How it works in practice
&lt;/h2&gt;

&lt;p&gt;Clutter worked. It parsed &lt;code&gt;.clutter&lt;/code&gt; files, validated tokens against the design system, emitted Vue SFCs. The problem was that a significant part of the compiler — lexer, parser, the entire pipeline — had been written in fast sessions, with the AI producing code while I tried to keep up. It worked, but it wasn't mine. There was no ownership: if something had broken in the middle, I wouldn't have known where to put my hands without starting to ask again — which is a valid approach, but not one that leads to a good product long term.&lt;/p&gt;

&lt;p&gt;When I decided to evolve Clutter into Origami — a larger, more ambitious project, with routing, data layer, i18n — it was clear I couldn't carry that fragility forward. I decided to rewrite the compiler from scratch because everything I would build on top of it depended on the solidity of that foundation. I needed to be its author, not just its requester.&lt;/p&gt;

&lt;p&gt;The starting point, then, was a step before the code. It was necessary to start from the grammar, the formal grammar that formal languages are normally built on.&lt;/p&gt;

&lt;p&gt;I opened a session with Claude with a precise instruction: no direct answers, only questions. The first thing I was asked wasn't technical:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; [...] You said that in a template there are three types of tags. Let's take the simplest one: the self-closing tag.&lt;br&gt;
Write in natural language — not in Bison — the sentence that completes this:&lt;br&gt;
"A self-closing tag is made up of..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wasn't sure I was enjoying it, but it forced me to put my ideas in order, as if I had to explain it to someone who knew nothing about the language. Not "how does the parser work", but "what are tags to you".&lt;br&gt;
I started answering and realized I was building the grammar from the inside, starting from how I thought about the language. Not from a spec, not from a Bison manual, from what I had in my head. The formal grammar that came out of it didn't describe an abstract language. It described exactly what I had in mind, made precise and verifiable.&lt;/p&gt;

&lt;p&gt;A formal grammar defines what a language can express and what it cannot. It's a contract. If you write the grammar first, you know exactly which constructs your parser must recognize, which checks belong to the lexer, which to the parser, which must be deferred to semantic analysis. It's the perimeter of what you're building, made explicit.&lt;/p&gt;

&lt;p&gt;What followed was about four hours of dialogue, spread across two sessions.&lt;/p&gt;



&lt;p&gt;One moment worth showing involves closing tags. In a &lt;code&gt;.ori&lt;/code&gt; file, every opened element must be closed, &lt;code&gt;&amp;lt;Column&amp;gt;...&amp;lt;/Column&amp;gt;&lt;/code&gt;. The question was whether the parser should verify that the opening tag name matches the closing one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Should the parser verify that &lt;code&gt;&amp;lt;Column&amp;gt;...&amp;lt;/Column&amp;gt;&lt;/code&gt; matches, or can you do without this check at the grammar level?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; I don't know... don't the rules we wrote serve that purpose?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; The rules use tokens to distinguish cases. If &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Column&amp;gt;&lt;/code&gt; emit the same &lt;code&gt;TAG_NAME&lt;/code&gt; token, the parser sees them as identical and can't apply different rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Does that matter for the lexer? They're tag symbols like any other, a tag name can be if or each too, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Exactly — from a character standpoint, if and &lt;code&gt;Column&lt;/code&gt; are both sequences of letters. The lexer could emit both as &lt;code&gt;TAG_NAME&lt;/code&gt;. But then who distinguishes a &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; from a &lt;code&gt;&amp;lt;Column&amp;gt;&lt;/code&gt; to apply the correct grammar rules?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A real fork opened up. I could keep distinct tokens for control tags, &lt;code&gt;OPEN_IF&lt;/code&gt;, &lt;code&gt;OPEN_EACH&lt;/code&gt;, &lt;code&gt;OPEN_UNSAFE&lt;/code&gt;, because their syntactic structure differs from a normal tag: they have specific mandatory attributes, they follow precise rules. Or I could use &lt;code&gt;TAG_NAME&lt;/code&gt; for everything and push the distinction past parsing. It wasn't an obvious technical answer. It was a design choice about where I wanted the controls to live.&lt;/p&gt;

&lt;p&gt;I kept the tokens distinct. A control tag has a structure the parser must recognize, it's not semantics, but syntax. That boundary was mine to draw, and I drew it consciously.&lt;/p&gt;



&lt;p&gt;The second moment involves &lt;code&gt;else&lt;/code&gt;. In the grammar I was building, an &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; block could be followed by zero or more &lt;code&gt;&amp;lt;else-if&amp;gt;&lt;/code&gt; blocks and a final &lt;code&gt;&amp;lt;else&amp;gt;&lt;/code&gt;. The question was whether the final else should be mandatory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Are you sure about this choice? Imagine being a developer writing a component. Consider this concrete case: &lt;code&gt;&amp;lt;if condition={isLoggedIn}&amp;gt;&amp;lt;UserProfile /&amp;gt;&amp;lt;/if&amp;gt;&lt;/code&gt;. Without else, what should the UI show when &lt;code&gt;isLoggedIn&lt;/code&gt; is false?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Nothing, but if you add an &lt;code&gt;else-if&lt;/code&gt;, isn't it logically sensible to also add an &lt;code&gt;else&lt;/code&gt;? Why don't other languages enforce this?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; In general-purpose languages &lt;code&gt;else&lt;/code&gt; isn't mandatory because "do nothing" is a legitimate and common case. Origami is a markup language — "do nothing" simply means render nothing. The question is: do you want Origami to enforce a more explicit style, or do you want to leave that freedom to the developer? You already decided to force reason in unsafe — is this the same philosophy?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; I'm not sure... no, not in this case. I think it would end up being frustrating for the developer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So &lt;code&gt;else&lt;/code&gt; stays optional, not because it's the objectively right answer, but because I evaluated the consequences and chose and the grammar now reflects that choice.&lt;/p&gt;



&lt;p&gt;In the second session I tried the same approach, this time applied to writing real code and real data structures. We talked about tokens, the atomic unit the lexer produces and the parser consumes, and how to implement them. The starting point was the &lt;code&gt;grammar.y&lt;/code&gt; file, already complete: all tokens were there, listed and classified. The task was to map them into a Rust enum.&lt;br&gt;
I proposed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;MARKER_PURE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;MARKER_WITH_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;WITH_VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&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;My ideas were confused and I had no real starting point, so I put this forward. The idea seemed reasonable on the surface: group tokens by category, carry the specific value in a string.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; When the parser encounters a token and needs to distinguish &lt;code&gt;OPEN_IF&lt;/code&gt; from &lt;code&gt;OPEN_EACH&lt;/code&gt;, how does it do that with a generic &lt;code&gt;MARKER_WITH_CONTEXT&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was: it can't. It would have to look inside the string, do a comparison, branch on that. I had shifted the problem from type to value, and Rust's type system wouldn't have been able to help me anywhere in the parser. Every match would have become a string comparison, fragile and silent on errors.&lt;/p&gt;

&lt;p&gt;The fix was obvious once seen: each token is its own variant. Markers, those whose value is always fixed, like &lt;code&gt;OPEN_IF&lt;/code&gt; or &lt;code&gt;DIVIDER&lt;/code&gt;, are unit variants, no attached data. Tokens that carry variable information &lt;code&gt;TagName(String)&lt;/code&gt;, &lt;code&gt;AttrName(String)&lt;/code&gt;, &lt;code&gt;ValueString(String)&lt;/code&gt;, have an explicit payload.&lt;/p&gt;

&lt;p&gt;During the revision I added &lt;code&gt;COMPONENT&lt;/code&gt; to the value-carrying tokens, noticing it can be component, page, or layout. I added &lt;code&gt;Eof&lt;/code&gt; by hand, because in Bison end-of-input is implicit; in Rust you have to declare it. And I kept &lt;code&gt;IF_CONDITION&lt;/code&gt; as a separate token instead of absorbing it into &lt;code&gt;OPEN_IF&lt;/code&gt;, because keeping it separate is the mechanism that lets the compiler say something like "expected condition, found cond" instead of a generic parse error when a wrong attribute is found, like &lt;code&gt;&amp;lt;if con={{isLoggedIn}}&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final enum was written by me, and more importantly, I understood it.&lt;/p&gt;




&lt;p&gt;This is the part that feels hardest to communicate about the Socratic approach, because it risks sounding trivial: I wasn't learning formal grammars, that wasn't the goal. I was learning to make decisions about formal grammars, and there's an enormous difference.&lt;/p&gt;

&lt;p&gt;The AI never told me that &lt;code&gt;CLOSE_TAG&lt;/code&gt; should be an opaque token, or that else should be optional, or that grouping tokens by category was the wrong abstraction. It asked the right questions until I was the one telling myself. Every choice that appears in &lt;code&gt;grammar.y&lt;/code&gt; and &lt;code&gt;tokens.rs&lt;/code&gt; is a choice I made explicitly, aware of its consequences. I can defend it, change it, explain why.&lt;/p&gt;

&lt;p&gt;It's a slower way to work, certainly, possibly ill-suited to these times, and probably so for the vast number of applications that need to exist, function, and disappear. But for ideas whose goal is to shift paradigms and act on multiple levels of a complex workflow, I think it's necessary. It requires having a rough idea of what you want to build, the AI doesn't generate the idea, it facilitates the process of making it precise. The result is real ownership, not mere possession of a file someone else wrote for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When the method isn't needed
&lt;/h2&gt;

&lt;p&gt;Not all the work on Origami was done this way. The CLI, the Cargo workspace configuration, the CI setup, the initial crate scaffolding, all of this was delegated without hesitation. No Socratic session to configure GitHub Actions, no maieutic questions to write the &lt;code&gt;Cargo.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This isn't a contradiction, these are parts of the project where there's no design decision to internalize, it's mechanical application of documented APIs and configuration that follows established conventions. This is where AI excels without constraints: it understands the context, generates the appropriate code, and having you do it yourself would just be a waste of time. The same applies, at different levels, to everything that comes after the codegen in the compilation pipeline, parts where the structure is already determined by upstream choices, and the work is implementation, not design.&lt;/p&gt;

&lt;p&gt;The Socratic method makes sense where there's a decision to be made that needs to be yours. Where that decision doesn't exist, it's just unnecessary friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The limits of the method
&lt;/h2&gt;

&lt;p&gt;There's something worth saying clearly, because the approach can seem more universal than it actually is.&lt;/p&gt;

&lt;p&gt;In the original Socratic dialogue, Socrates already knows where he wants to go: the questions aren't genuine, they're maieutic, designed to bring out in the interlocutor a knowledge they already possess in some form. If Socrates didn't know where he was going, the dialogue wouldn't be maieutics, it would just be conversation.&lt;/p&gt;

&lt;p&gt;The same applies here, this approach worked because there was a precise balance: I knew nothing about formal grammars, but I knew exactly what I wanted to build and I had a very clear language in my head, its syntax, its constructs and its boundaries. What I was missing was the formalization, the tool to make it precise and verifiable. The AI helped bridge that gap, asking questions that started from what I knew to arrive at what I didn't yet know how to express.&lt;/p&gt;

&lt;p&gt;If that balance isn't there, if both the theoretical knowledge and the knowledge of the specific problem are missing, the approach doesn't work, or worse, works badly. You can arrive at answers that seem coherent without having the tools to realize they're wrong. Socratic questions need ground to work on, if that ground isn't there, it's not maieutics , it's the blind leading the blind, with the illusion that someone knows where they're going.&lt;/p&gt;

&lt;p&gt;You don't need to be an expert in the technical domain, but you do need a solid and clear understanding of the problem you want to solve, the intention and the specific context. That part can't come from the AI, because it's exactly what the AI doesn't have. It's what you bring.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. One way among many
&lt;/h2&gt;

&lt;p&gt;There's a real tension in this moment for anyone who's been doing this work for a while. The tools that have arrived in recent years seem to call into question the value of what you've learned, the experience, the knowledge, the time invested. It's an uncomfortable feeling, and I understand those who get stuck on it.&lt;br&gt;
I don't have a general answer, I only have what I did: I used these tools to build something I couldn't have built without them, not for lack of ability (well maybe...), but for lack of time and specific knowledge I didn't have and had no reason to acquire before. Origami will exist because I used, and I will use, AI well, which means, in my case, using it where it accelerates without taking anything away from me, and keeping it in check where decisions need to remain mine.&lt;br&gt;
It's not the only way to work. It's one of the ways. For me, on this project, it worked.&lt;/p&gt;




&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/lucabro81/clutter" rel="noopener noreferrer"&gt;Clutter&lt;/a&gt; A Rust compiler for &lt;code&gt;.clutter&lt;/code&gt;, a UI markup language with a closed vocabulary that enforces design system compliance at compile time&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lucabro81/origami" rel="noopener noreferrer"&gt;Origami&lt;/a&gt; A fullstack opinionated framework with a closed-vocabulary DSL that enforces design system compliance at compile time.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lucabro81/origami/blob/clutter-port/grammar.y" rel="noopener noreferrer"&gt;Origami grammar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: this article was written with the assistance of AI as a collaborative drafting tool. The ideas, decisions, and experiences described are mine, and everything generated was carefully reviewed and revised by me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>compiling</category>
      <category>socrate</category>
      <category>control</category>
    </item>
  </channel>
</rss>
