<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>baseballyama&apos;s Blog</title>
		<link>https://blog.baseballyama.com/blog</link>
		<atom:link href="https://blog.baseballyama.com/rss.xml" rel="self" type="application/rss+xml" />
		<description>Yuichiro Yamashita (baseballyama) — software engineer working on compilers, parsers and static analysis. Svelte core team, VP of Technology at Flyle.</description>
		<language>en</language>
		<item>
			<title>rsvelte: Rebuilding the Svelte Toolchain in Rust for the AI Era</title>
			<link>https://blog.baseballyama.com/posts/20260721-rsvelte</link>
			<guid>https://blog.baseballyama.com/posts/20260721-rsvelte</guid>
			<pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>At [Flyle](https://flyle.io), running multiple AI agents in parallel has become a normal part of development. In this w</description>
			<content:encoded><![CDATA[<p>At <a href="https://flyle.io">Flyle</a>, running multiple AI agents in parallel has become a normal part of development. In this workflow, agents run type checking and linting after each implementation step, making static checks far more frequent than in primarily human-driven development. When checks run in parallel, they compete for CPU and memory, increasing the latency of each run. An agent cannot start its next fix until the check finishes, so <strong>the time a static check takes sets a lower bound on the duration of each agent loop iteration</strong>.</p>
<p>In Svelte projects, these static checks are especially slow. To solve this, I am building <a href="https://github.com/baseballyama/rsvelte">rsvelte</a>, a project to rebuild the Svelte toolchain in Rust. This article explains why this matters now and what rsvelte does.</p>
<h2>tl;dr</h2>
<ul>
<li>AI agents run static checks on every loop iteration, so check time sets a lower bound on iteration time, and CPU/memory usage limits how many agents can run in parallel</li>
<li>Rust and other native toolchains are delivering order-of-magnitude speedups in parts of the static-analysis stack, but the Svelte-specific parts of <code>.svelte</code> processing (parsing, transformation, linting, formatting) still rely on JavaScript implementations</li>
<li>rsvelte is a set of Rust implementations that aims to be a drop-in replacement for the Svelte toolchain. It does not introduce any language-level extensions. It is early stage, and maturity varies by package</li>
<li>On Flyle&#39;s production frontend, with 8,795 files in the official check scope, replacing the official svelte-check path with rsvelte-check cut type checking from 51.4s to 30.6s (1.7x, about 56% less CPU, while keeping the JavaScript implementation of TypeScript 5.9.3). Switching the check engine to tsgo brought it to 9.0s (5.7x combined)</li>
<li>With an identical set of Svelte-specific rules, rsvelte-lint is about 20x faster, and all 382 diagnostics match</li>
<li>With eight simultaneous checks, the type-checking gap narrows because the TypeScript phase common to both setups dominates. Shorter individual runs should still reduce overlap in normal use, though this remains to be verified with real agent traces</li>
</ul>
<h2>1. Why check speed matters now</h2>
<h3>1.1 AI takes on implementation, and quality assurance shifts toward harnesses</h3>
<p>As AI agents take on more implementation work, the way we ensure quality changes. Static checks mattered in human-centered development too. But as agents take on more of the implementation work, <strong>automated harnesses that verify their output</strong> take on a larger share of quality assurance.</p>
<p>I think of these harnesses in several layers: design harnesses that establish and constrain the assumptions agents work from through documentation and types, development harnesses that verify output after each implementation step, and test harnesses that verify behavior. Each is worth its own article, but this one focuses on the core of the development harness: the <strong>static analysis stack</strong> (formatter, type checking, linter, unused-code detection).</p>
<h3>1.2 The execution model of static checks changed</h3>
<p>Static checks themselves are nothing new. What changed is how they run.</p>
<p>Static checks mattered before AI too. But it was rare for one developer to launch full checks many times in a short period, or to run the same checks in parallel across multiple working trees. In agent-driven development, both are normal.</p>
<p>Agents work differently from humans. Some agents, like Codex CLI, do not use a Language Server at all. Even when an agent can use one, like Claude Code, parallel instances do not necessarily share a Language Server. If each instance starts its own, the analysis work and memory usage multiply with the number of instances. Also, LSP diagnostics and settings do not always match CI, so in many setups you still need a CLI check identical to CI as the final quality gate. An agent runs checks many times in a single task, and running multiple agents in parallel multiplies the number of concurrent checks. In short, static checks used to run at low frequency and low parallelism. Now they run at high frequency and high parallelism.</p>
<p>To be precise, this is not a problem unique to AI. CI and pre-commit hooks have run full CLI checks for a long time, and slow full checks are a well-known problem. What changed is the frequency and the parallelism. Static-analysis-heavy workflows had already been growing more common for years; parallel AI development pushed the trend to an extreme. That is why this article talks about AI, but the argument below applies to any workload that runs many full CLI checks.</p>
<h3>1.3 Slowness costs you in two ways</h3>
<p>Slow checks cost you in two connected ways: latency and resource usage.</p>
<p>The first is <strong>latency</strong>. In a loop that waits for check results before deciding the next fix, check time is the critical path. If a check takes 5 minutes, one iteration takes at least 5 minutes. In my experience, it is not unusual for a full check suite to take several minutes. Even on Flyle&#39;s production frontend, measured later in this article, type checking alone takes about 51 seconds (3.3). At that rate, running the check five times in one task adds more than four minutes of type-checking latency.</p>
<p>The second is <strong>resources</strong>. The current Svelte check stack uses a lot of CPU and memory across its whole process tree, including the TypeScript engine and the transformation step. In the measurements below (3.3), the current JS setup used a peak of 4.2GB of memory and about 105 CPU-seconds for one type check of Flyle&#39;s production frontend. Running several checks in parallel can exhaust the CPU and memory of a developer machine. So check resource usage becomes one of the main limits on how many agents you can run at once.</p>
<h3>1.4 Why &quot;just use CI&quot; and &quot;just buy a bigger machine&quot; are not enough</h3>
<p>Moving checks to CI only delays the feedback. Agents use check results to decide their next fix, so fast feedback inside the loop matters. If implementation continues without checks, new work piles up on top of wrong assumptions, and fixing errors found in bulk later costs more. To guide the next implementation step, the check needs to run inside the loop.</p>
<p>A bigger machine does help. But if each check process uses the same amount of resources, the required CPU and memory grow as parallelism increases. Making the tools more efficient increases the number of agents the same hardware can support.</p>
<h2>2. Rust-based toolchains and where Svelte stands</h2>
<h3>2.1 Static-analysis tools are moving to native implementations</h3>
<p>Static-analysis tools are increasingly being rewritten as native-code implementations in Rust, Go, and similar languages.</p>
<ul>
<li>oxlint, from the <a href="https://oxc.rs/">oxc</a> project, runs linting 50-100x faster than ESLint (as claimed by the project)</li>
<li><a href="https://github.com/microsoft/typescript-go">tsgo</a>, a Go port of the TypeScript compiler, claims about 10x faster type checking than tsc (<a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">A 10x Faster TypeScript</a>)</li>
<li><a href="https://biomejs.dev/">Biome</a> applies the same native-toolchain approach to formatting and linting</li>
</ul>
<p>This speedup is not just &quot;write it in Rust and it gets fast.&quot; It comes from the entire design: parallel-first architecture, avoiding unnecessary repeated parsing, memory-efficient data structures, and the performance characteristics of the implementation language.</p>
<p>The important point is that when speed changes this much, behavior changes too. Checks that take 10 minutes overlap across parallel agents and contend for CPU and memory. Shorter checks reduce the window in which concurrent runs can overlap, making resource contention less likely. Better latency can also relieve the resource problem by reducing overlap.</p>
<h3>2.2 But the Svelte-specific parts are still JavaScript-based</h3>
<p>Svelte-specific processing has not fully caught this wave.</p>
<ul>
<li><strong>Linting</strong>: oxlint has an alpha-stage feature that extracts and checks the script part of <code>.svelte</code> files, but it does not yet provide a foundation for checking Svelte-specific semantics across templates and styles. Svelte-specific rules still depend on the JavaScript-based eslint-plugin-svelte + ESLint</li>
<li><strong>Formatting</strong>: oxfmt&#39;s Svelte support delegates the Svelte structure to the JavaScript-based prettier-plugin-svelte and hands only embedded JS/TS to oxc_formatter (embedded CSS goes through Prettier&#39;s built-in CSS formatter). The <code>.svelte</code> structure and embedded CSS are therefore still parsed and formatted through the Prettier-based path</li>
<li><strong>Type checking</strong>: svelte-check converts <code>.svelte</code> files to TypeScript with svelte2tsx and passes them to a check engine. The engine can now be sped up with <code>--tsgo</code>, but the transformation and orchestration remain JavaScript-based</li>
</ul>
<p>In other words, even the official tools can now speed up the TypeScript part with tsgo, but the Svelte-specific work (Svelte parsing, svelte2tsx transformation, template linting, formatting) still relies on JavaScript implementations. This is not unique to Svelte. Frameworks with their own template languages, such as Vue, face the same problem.</p>
<h2>3. rsvelte</h2>
<p><a href="https://github.com/baseballyama/rsvelte">rsvelte</a> is a project to close this gap.</p>
<h3>3.1 Goal: a drop-in replacement for the Svelte toolchain</h3>
<p>rsvelte&#39;s long-term goal is to be a <strong>drop-in replacement</strong> for the existing Svelte toolchain: config files and commands stay the same, and only the implementation changes to Rust. Its design principle is to <strong>preserve Svelte&#39;s language semantics and compiler behavior rather than adding its own extensions</strong> (it does have tool-level extras such as CLI flags and caching).</p>
<p>The reason is to make switching and rollback cost as close to zero as possible in the end. Alternative tools with unique features create dependencies on those features as soon as you adopt them, making it progressively harder to return to the original toolchain. If compatibility is preserved, you can adopt, measure, and roll back safely if something goes wrong. This resembles one part of oxlint&#39;s strategy: earning trust by faithfully porting existing ESLint rules. This compatibility is also a prerequisite for eventually proposing maintenance under the Svelte organization, discussed later.</p>
<p>Compatibility is backed by verification, not by declaration. The compiler passes 100% of the 3,500+ in-scope fixtures of the official Svelte v5.56.4 test suite (the Svelte 4 to 5 migrator and a few individual fixtures are out of scope). On top of that, code from about 30 real-world repositories (about 12,000 compilation units) is continuously compiled with both the official compiler and rsvelte, and the outputs are compared. The known structural differences currently stand at 8 for client output and 0 for server output (both as of commit <code>76ac14b3</code>; the README and dashboard in the repository may show different numbers depending on when they were updated). CI treats the known-difference list as a ratchet, failing if the number of differences increases, and each difference has its cause documented.</p>
<p>That said, how close each package is to drop-in status varies a lot. Passing 100% of fixtures does not mean full public-API compatibility. Constraints remain around options that accept functions, such as <code>cssHash</code>, and the status of each package is in the table in 3.2. Strictly speaking, the current state is not &quot;a finished drop-in replacement&quot; but &quot;a set of compatible implementations moving toward drop-in status through continuous verification.&quot;</p>
<h3>3.2 Components</h3>
<p>rsvelte ships each tool of the static analysis stack as a separate package. Because maturity varies a lot, the table lists both what each package is and where it stands.</p>
<table>
<thead>
<tr>
<th>Area</th>
<th>Package</th>
<th>What it is</th>
<th>Current status</th>
</tr>
</thead>
<tbody><tr>
<td>Compiler</td>
<td><code>@rsvelte/compiler</code></td>
<td>Rust port of the Svelte 5 compiler</td>
<td>100% of in-scope fixtures pass. Known structural output differences on real code: client 8 / server 0</td>
</tr>
<tr>
<td>Build</td>
<td><code>@rsvelte/vite-plugin-svelte</code></td>
<td>Fork of the official vite-plugin-svelte (same API)</td>
<td>Experimental</td>
</tr>
<tr>
<td>Type check (transform)</td>
<td><code>@rsvelte/svelte2tsx</code></td>
<td>Rust port of svelte2tsx</td>
<td>0 known output differences. The API is async (for WASM initialization), unlike upstream</td>
</tr>
<tr>
<td>Type check (CLI)</td>
<td><code>@rsvelte/svelte-check</code></td>
<td>Port of svelte-check; the check engine can be tsc or tsgo</td>
<td>Early stage. Some CLI flags differ. Not yet recommended as a CI gate without running the official version alongside</td>
</tr>
<tr>
<td>Formatting</td>
<td><code>@rsvelte/fmt</code></td>
<td>Port of prettier-plugin-svelte; works with oxfmt</td>
<td>40 known output differences on real code. Configured via .oxfmtrc (does not read Prettier config)</td>
</tr>
<tr>
<td>Lint</td>
<td><code>@rsvelte/lint</code></td>
<td>Ports 80 rules from eslint-plugin-svelte</td>
<td>Currently a complement to ESLint, not a replacement</td>
</tr>
<tr>
<td>Editor</td>
<td><code>@rsvelte/language-server</code> / <code>rsvelte-vscode</code></td>
<td>Language Server and VS Code extension (separate packages)</td>
<td>Formatting and linting only. No type checking, completion, or go-to-definition</td>
</tr>
</tbody></table>
<p>Building all these tools together is a consequence, not a goal. The formatter, type checker, and linter all depend on a Svelte parser. But using the JavaScript Svelte compiler from native tools requires crossing a JS runtime or a process boundary, and it cannot plug directly into oxc&#39;s AST and semantic pipeline. Sharing one Rust parser (built on oxc) across all tools is the precondition for the oxc integration discussed later.</p>
<p>Type checking needs one more note. The official svelte-check already has a <code>--tsgo</code> option, so both tools can use the same TS engine. rsvelte&#39;s difference is the Rust implementation of the svelte2tsx transformation and the orchestration. The benchmarks below therefore include comparisons where the TypeScript implementation is held constant, to isolate the Svelte-side difference as much as possible.</p>
<h3>3.3 Benchmarks</h3>
<p>I ran the following benchmarks on my own machine for this article. Before reading the numbers, keep two kinds of measurement separate: <strong>engine throughput</strong> (the transformation and formatting work itself, on files already loaded in memory) and <strong>end-to-end</strong> (from CLI start to process exit, which is what a user experiences). Engine-level speedups do not translate directly into the same end-to-end speedups.</p>
<p>Main conditions: Apple M4 Pro (12 cores) / 48GB, Node.js 24.13.1, rsvelte commit <code>76ac14b3</code> (built from source), Svelte v5.56.4, svelte-check 4.7.3, typescript 5.9.3, tsgo 7.0.0-dev.20260707.2, eslint 10.7.0, eslint-plugin-svelte 3.21.0. All comparisons were run on the same machine and the same corpus, and the reported values are <strong>medians</strong> across multiple runs. The measurement scripts and raw data are in the rsvelte repository under <code>scripts/bench/</code> (the latest engine-level results and charts are on the <a href="https://baseballyama.github.io/rsvelte/benchmark">benchmark page</a>).</p>
<details>
<summary>Measurement details</summary>
<ul>
<li>CPU time comes from <code>/usr/bin/time -l</code> (user + sys, including awaited child processes). RSS is an estimate: process-tree totals sampled with <code>ps</code> every 100ms, which can double-count shared pages. The split between the checker itself and the TS engine is based on PID parent-child relationships, so it is an estimate based on process structure</li>
<li>Check tools write overlay artifacts into the workspace, and these affect later runs. So flowbite measurements use a fresh workspace per run via APFS clonefile: no tool caches, non-incremental (OS page caches are not controlled)</li>
<li>The flowbite workload reports about 900 errors in both tools (js 885 / rs 926; 669 match by file and message). The setup (<code>pnpm install --ignore-scripts</code>, adding typescript / @typescript/native-preview, <code>svelte-kit sync</code>) does not reproduce the full library-development environment, which causes module-resolution errors. These numbers are not a healthy CI result but <strong>a reference for performance trends</strong></li>
<li>Flyle measurements reset state before every run via <code>svelte-kit sync</code> and overlay removal, taking the median of 5 runs after 1 warmup</li>
<li>Lint setup: the ESLint side applies eslint-plugin-svelte (flat/recommended) plus the TS parser to all 1,296 files under <code>src</code>, with all non-svelte rules and unused-directive reporting disabled. The rsvelte-lint side runs a config that imports the 37 svelte/* rules at identical severities from ESLint's resolved config (based on <code>extends: ["none"]</code>)</li>
<li>One of the 37 rules, <code>svelte/no-unused-props</code>, is excluded from the comparison. The ESLint implementation needs TypeScript type information and detects nothing in this setup, while rsvelte-lint detects 11 findings without type information. To keep the workload and output identical on both sides, I disabled it on the rsvelte side</li>
</ul>
</details><h4>3.3.1 End-to-end type checking (Flyle production)</h4>
<p>First, consider the end-to-end experience of a user or an agent. I measured on Flyle&#39;s production frontend (a SvelteKit app; the official svelte-check includes 8,795 files in its check scope). The total improvement comes from two changes.</p>
<pre><code>svelte-check + TypeScript Language Service   51.4s   &lt;- current CI configuration
      | switch to rsvelte-check, keep TypeScript 5.9.3 (JavaScript)
rsvelte-check + tsc                          30.6s   (1.7x; about 56% lower CPU time: 104.9s -&gt; 45.9s)
      | switch the check engine to tsgo
rsvelte-check + tsgo                          9.0s   (5.7x combined; CPU time: 32.1s)
</code></pre>
<table>
<thead>
<tr>
<th>Configuration</th>
<th align="right">wall time (range)</th>
<th align="right">CPU time</th>
<th align="right">peak RSS total</th>
<th align="right">checker itself</th>
</tr>
</thead>
<tbody><tr>
<td>svelte-check + TypeScript Language Service (current CI configuration)</td>
<td align="right">51.4s (46.9-64.4)</td>
<td align="right">104.9s</td>
<td align="right">4.2GB</td>
<td align="right">inseparable (in-process LS)</td>
</tr>
<tr>
<td>rsvelte-check + tsc</td>
<td align="right">30.6s (29.8-36.2)</td>
<td align="right">45.9s</td>
<td align="right">3.8GB</td>
<td align="right">0.12GB</td>
</tr>
<tr>
<td>rsvelte-check + tsgo</td>
<td align="right">9.0s (8.6-9.9)</td>
<td align="right">32.1s</td>
<td align="right">5.5GB</td>
<td align="right">0.12GB</td>
</tr>
</tbody></table>
<p><strong>Replacing the official svelte-check path with rsvelte-check produces a 1.7x speedup</strong> (this replacement includes both the Rust implementation of the Svelte side and the change of execution path from the Language Service to the tsc CLI). <strong>Adding tsgo brings the combined speedup to 5.7x</strong>. The official svelte-check run and both rsvelte-check runs all reported 0 errors; the official run reported 44 warnings, while both rsvelte-check runs reported 41. The 3-warning difference comes from a single file at the workspace boundary that only the official version includes in its scope (the rs side does not print a checked-file count in machine-readable output, so the file sets could not be fully reconciled). For memory usage, rsvelte-check itself peaks at <strong>about 0.12GB</strong> with either engine, and in the rsvelte setups the TS engine takes most of the memory (most of the 5.5GB total in the tsgo setup comes from the tsgo process; comparing the two setups that both use the JavaScript TypeScript implementation, total peak RSS drops from 4.2GB to 3.8GB). The official svelte-check&#39;s <code>--tsgo</code> did not work correctly on this repository: it shrank the check scope to 355 files and reported 1,581 spurious errors, so I excluded it from the comparison (I reproduced the issue with svelte-check 4.7.3).</p>
<h4>3.3.2 Comparison with tsgo on both sides (flowbite-svelte)</h4>
<p>Two comparisons were not practical on the Flyle repository: a comparison with tsgo on both sides (because the official <code>--tsgo</code> malfunctions there) and parallel execution (which needs N independent workspace copies, impractical for a 17GB production monorepo). I ran both on <a href="https://github.com/themesberg/flowbite-svelte">flowbite-svelte</a> (commit <code>85f20a0</code>, 1,296 <code>.svelte</code> files from a real-world project), where the official <code>--tsgo</code> works. One important limitation: this setup does not reproduce the full Flowbite Svelte development environment, so both tools report roughly 900 diagnostics, mostly related to module resolution. The numbers are useful for performance trends, not for correctness parity (details in &quot;Measurement details&quot; above). With tsgo on both sides, <strong>10 interleaved pairs</strong> (with the execution order alternating between pairs and a fresh workspace for every run) gave a median of 3.96s (3.84-4.16) for the official svelte-check and 2.12s (2.03-2.36) for rsvelte-check. The median ratio within each pair was <strong>1.9x</strong> (1.72-1.96). This is much smaller than the engine-level multipliers because, end to end, the TypeScript-checking phase common to both setups takes most of the wall time.</p>
<h4>3.3.3 Parallel execution</h4>
<p>Next, I measured the scenario most directly related to the article&#39;s main argument: <strong>N check processes launched at the same time on one machine</strong> (flowbite-svelte, tsgo on both sides; the svelte-check + Language Service setup was excluded because its run-to-run variance under parallelism was too large for stable numbers). Table values are &quot;elapsed time until all N runs complete / total CPU time / peak aggregate RSS across all concurrently running process trees.&quot;</p>
<table>
<thead>
<tr>
<th>Concurrency</th>
<th>svelte-check + tsgo</th>
<th>rsvelte-check + tsgo</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>4.0s / 8.1s / 1.3GB</td>
<td>2.1s / 6.0s / 1.2GB</td>
</tr>
<tr>
<td>2</td>
<td>4.7s / 17.3s / 2.4GB</td>
<td>3.5s / 13.1s / 2.2GB</td>
</tr>
<tr>
<td>4</td>
<td>8.1s / 39.7s / 4.2GB</td>
<td>6.7s / 28.6s / 4.3GB</td>
</tr>
<tr>
<td>8</td>
<td>14.1s / 86.0s / 6.9GB</td>
<td>13.5s / 67.8s / 6.7GB</td>
</tr>
</tbody></table>
<p>The 1.9x gap at N=1 shrinks as concurrency rises, and it nearly disappears with 8 simultaneous checks (14.1s vs 13.5s). This result suggests that <strong>under a high-load simultaneous-start workload, the TypeScript-checking phase common to both setups becomes the bottleneck</strong> (CPU time per check, total CPU time divided by eight, is still about 21% lower at 10.8 vs 8.5 CPU-seconds, and total RSS is similar; capping rayon threads at 12/N made no significant difference). In real use, however, check start times are spread out, so I believe shorter individual runs reduce the amount of time for which checks overlap. This simultaneous-launch benchmark does not measure that directly; verifying it with real agent execution logs is future work.</p>
<h4>3.3.4 Svelte-specific linting</h4>
<p>I used the same paired-run methodology for linting (see &quot;Measurement details&quot; for the setup). This is not a comparison of the full ESLint configuration. I enabled the Svelte-specific rules of flat/recommended at identical severities on both sides and disabled every other rule. There is one exception: <code>svelte/no-unused-props</code> needs TypeScript type information in the ESLint implementation and detects nothing in this setup, while rsvelte-lint detects 11 findings without type information. To keep the workload and output identical, I disabled that rule on the rsvelte side and excluded it from the comparison (details in &quot;Measurement details&quot;). With that, both tools produced <strong>exactly the same 382 diagnostics</strong>.</p>
<table>
<thead>
<tr>
<th></th>
<th align="right">wall time</th>
<th align="right">CPU time</th>
<th align="right">peak RSS</th>
</tr>
</thead>
<tbody><tr>
<td>ESLint + eslint-plugin-svelte</td>
<td align="right">5.02s</td>
<td align="right">8.4s</td>
<td align="right">0.86GB</td>
</tr>
<tr>
<td>rsvelte-lint</td>
<td align="right">0.25s</td>
<td align="right">1.7s</td>
<td align="right">0.05GB</td>
</tr>
</tbody></table>
<p>The median ratio across 10 interleaved pairs was <strong>19.4x</strong> (range 18.0-23.5). Unlike type checking, this comparison has no large shared TypeScript phase dominating the end-to-end runtime, so the speedup remains close to 20x even end to end. Memory also drops sharply, from 0.86GB to 0.05GB.</p>
<h4>3.3.5 Engine-level performance</h4>
<p>Finally, the engine-level results help explain the end-to-end speedups (3,857 <code>.svelte</code> files drawn from the official Svelte test suite, pre-loaded into memory; median of 10 runs after 3 warmups):</p>
<table>
<thead>
<tr>
<th>Task</th>
<th align="right">JS</th>
<th align="right">rsvelte (1 thread)</th>
<th align="right">rsvelte (multi)</th>
<th align="right">Multiplier (multi)</th>
</tr>
</thead>
<tbody><tr>
<td>Parse</td>
<td align="right">149.3ms</td>
<td align="right">9.0ms</td>
<td align="right">1.9ms</td>
<td align="right">79.1x</td>
</tr>
<tr>
<td>Compile (client)</td>
<td align="right">625.9ms</td>
<td align="right">202.8ms</td>
<td align="right">32.9ms</td>
<td align="right">19.0x</td>
</tr>
<tr>
<td>Compile (SSR)</td>
<td align="right">510.4ms</td>
<td align="right">114.1ms</td>
<td align="right">16.9ms</td>
<td align="right">30.2x</td>
</tr>
<tr>
<td>svelte2tsx</td>
<td align="right">231.6ms</td>
<td align="right">91.2ms</td>
<td align="right">11.4ms</td>
<td align="right">20.4x</td>
</tr>
<tr>
<td>Format</td>
<td align="right">2891.6ms</td>
<td align="right">117.0ms</td>
<td align="right">23.7ms</td>
<td align="right">122.2x</td>
</tr>
<tr>
<td>svelte-check (tool-side work only)</td>
<td align="right">875.2ms</td>
<td align="right">41.6ms</td>
<td align="right">15.6ms</td>
<td align="right">56.2x</td>
</tr>
</tbody></table>
<p>Three caveats about this table. First, the svelte-check row disables TypeScript checking and TSX overlay generation in both tools, comparing only file traversal and Svelte-side parsing, analysis, and diagnostics. Second, this table measures API calls; CLI startup, file discovery, and config resolution are not included. In other words, the result means &quot;the format engine&#39;s throughput is about 120x on 12 cores,&quot; not &quot;the format command feels 120x faster.&quot; Third, the corpus includes fixtures that intentionally fail to compile (the script catches and ignores JS-side exceptions), and it does not verify that the success and failure sets match exactly between the two tools.</p>
<p>This answers the two costs raised in 1.3. <strong>Latency</strong>: a production type check becomes 1.7x faster after switching to rsvelte-check with the TypeScript implementation unchanged, and 5.7x faster with tsgo added (51.4s to 9.0s); Svelte-specific linting becomes about 20x faster. <strong>Resources</strong>: type-checking CPU time drops about 56% from the rsvelte-check switch alone (104.9s to 45.9s) and to 32.1s combined with tsgo. rsvelte-check itself peaks at about 0.12GB, while rsvelte-lint peaks at about 0.05GB. In the rsvelte setups the TypeScript engine takes most of the memory, and choosing tsgo increases the engine&#39;s share.</p>
<h2>4. Trying it today</h2>
<p>rsvelte is pre-1.0 and early stage; APIs may change without notice. Known constraints and per-package maturity are in the table in 3.2. With that in mind, each package can be adopted independently. See the <a href="https://github.com/baseballyama/rsvelte#readme">rsvelte README</a> for setup instructions.</p>
<p>Because the goal is a drop-in replacement, you can run it alongside the existing tools and compare the output. I recommend starting with rsvelte-check because it does not rewrite source files. Run it alongside the official checker and compare the diagnostics. For the formatter, start in <code>--check</code> mode or on an isolated branch, because known output differences remain.</p>
<h2>5. Where this is heading</h2>
<p><strong>Native integration with oxc.</strong> The oxc project has an implementation plan for accepting external language plugins (<a href="https://github.com/oxc-project/oxc/discussions/21936">oxc-project/oxc#21936</a>), and Svelte is among the targets. The implementation is still at an early stage, and there is no timeline for integration. Once it lands, oxlint and oxfmt could process <code>.svelte</code> files natively (instead of falling back to prettier), and users could adopt the oxc toolchain without knowing rsvelte exists.</p>
<p><strong>A possible future under the Svelte organization.</strong> Nothing is decided here. Long term, I hope to propose moving the project under the Svelte organization. A toolchain needs community trust and continuity, and a personal repository has limits. I see the compatibility-first design principle as the groundwork for one day reaching a level where that proposal can be made.</p>
<h2>Summary</h2>
<ul>
<li>As AI agents take on more implementation work, static checks increasingly run as part of every implementation loop. Check time sets a lower bound on iteration time, and check resource usage limits parallelism</li>
<li>Rust and other native toolchains are delivering major speedups across the static-analysis stack, but the Svelte-specific parts of <code>.svelte</code> processing still rely on JavaScript implementations</li>
<li>rsvelte is a set of Rust implementations that aims to be a drop-in replacement for the Svelte toolchain. It aims to preserve Svelte&#39;s language semantics and compiler behavior exactly, and it prioritizes low switching and rollback costs. Maturity varies by package</li>
<li>On Flyle&#39;s production frontend, switching to rsvelte-check while keeping the JavaScript implementation of TypeScript made type checking 1.7x faster (51.4s to 30.6s, about 56% less CPU), and 5.7x combined with tsgo (9.0s). Svelte-specific linting with an identical rule set is about 20x faster. rsvelte-check itself peaks at about 0.12GB; in rsvelte-based configurations, the TypeScript engine consumes most of the total memory. You can try it alongside the official tools while comparing diagnostic differences</li>
</ul>
<p>rsvelte is still a work in progress. If you try it and find problems, issues and feedback on <a href="https://github.com/baseballyama/rsvelte">GitHub</a> are very welcome.</p>
<hr>
<p>At <a href="https://herp.careers/v1/flyle">Flyle, Inc.</a>, where I serve as VP of Technology, we are currently hiring software engineers.
If this article resonated with you, if you are interested in building development infrastructure for the AI era, or if you are curious about our business, let us talk in a casual interview.</p>
]]></content:encoded>
		</item>
		<item>
			<title>How to Approach Abstraction and Consolidation</title>
			<link>https://blog.baseballyama.com/posts/20260211-abstraction-and-generalization</link>
			<guid>https://blog.baseballyama.com/posts/20260211-abstraction-and-generalization</guid>
			<pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>If you have ever been involved in software design, you will not disagree that &quot;data design&quot; is one of the important ele</description>
			<content:encoded><![CDATA[<p>If you have ever been involved in software design, you will not disagree that &quot;data design&quot; is one of the important elements of design.<br>Note that &quot;data design&quot; in this article is not limited to database table design. I use it in a sense that also covers how you organize business concepts and where you draw boundaries (domain modeling).</p>
<p>What I most want to convey in this article is simple.</p>
<p><strong>Consolidation is not a coding technique; it is a data-design decision (i.e., which concepts you treat as the same, and where you separate them).</strong><br>And <strong>abstraction is inherently hard and takes time</strong>. That is exactly why we should have options other than &quot;hurry up and merge everything into one.&quot;</p>
<h2>tl;dr</h2>
<ul>
<li>Consolidation is not a coding technique; it is a data-design decision (which concepts you treat as the same)  </li>
<li>Consolidating based only on &quot;surface-level duplication&quot; in the code tends to break down under future changes  </li>
<li>Abstraction is inherently hard, and the right answer can change as premises change  </li>
<li>Abstraction is the act of determining what is invariant and what is variable  </li>
<li>When in doubt, tolerate duplication (Avoid Hasty Abstraction / Three Strikes Rule)  </li>
<li>When in doubt, stop and ask again: &quot;are these really the same concept?&quot;</li>
</ul>
<h2>1. Do not be fooled by the &quot;look&quot; of the code</h2>
<p>The most important trap, and the easiest one to fall into, is focusing on &quot;code as written text&quot; itself.</p>
<p>Code is nothing more than the final output of writing down, in the language of software, the &quot;concepts&quot; that were organized through domain analysis and data design. So no matter how similar two pieces of code look, that alone does not make them candidates for consolidation.</p>
<p>For example, picture a shark and a dolphin. Both adopt the survival strategy of &quot;moving fast underwater,&quot; so if you write that behavior as code, you will end up with logic that looks very similar at first glance. But if you consolidate them as &quot;the same thing,&quot; the design is likely to break down quickly, because their semantics differ fundamentally.</p>
<p>A shark is a fish; a dolphin is a mammal. If you implement a &quot;breathing&quot; operation, for example, a shark needs gill-breathing logic, while a dolphin requires entirely different logic: lung breathing at the surface. If you had forced them together, the center of the consolidation (the base class or shared function) would fill up with <code>if</code> branches to split the breathing methods, and it would break easily with every change.</p>
<p>The point I want to emphasize here is this.</p>
<blockquote>
<p>The state of &quot;the code is duplicated&quot; is not necessarily a design flaw; sometimes it is<br>merely a signal that &quot;the concept to be consolidated has not matured enough yet.&quot;</p>
</blockquote>
<p>In other words, if you are captured by &quot;how the current code looks,&quot; you lose the ability to think in terms of the &quot;origin of the concept&quot; and the &quot;reasons for change&quot; behind it. When you take on abstraction and consolidation, you should focus on the structured &quot;meaning&quot; behind the phenomenon that is the code.</p>
<h2>2. Data design decides &quot;what to consolidate and where to separate&quot;</h2>
<p>At the beginning of this article, I wrote that &quot;data design&quot; is the work of restructuring real-world concepts into a form the system can handle. This is generally called data modeling.</p>
<p>Data modeling is the work of organizing which entities exist and what attributes each of them has, and then defining the relationships between entities. Put differently, it is also the work of deciding <strong>whether to treat similar things as the same (consolidate them) or to separate them as distinct things</strong>.</p>
<p>Even when you are dealing with the same &quot;shark&quot; and &quot;dolphin,&quot; the reasonable design changes depending on the system you are building. Let us think about this with two examples.</p>
<h3>A: A biological simulation system for a research institute</h3>
<p>(I have never worked on one, but) in a biological simulation system, doing data design based on biological classification would likely be one strong option. In that case, the data model might look like this.</p>
<pre class="mermaid">classDiagram
    class Animal {
        +name: string
    }
    class Fish {
        +breathing: string = "gills"
    }
    class Mammal {
        +breathing: string = "lungs"
    }
    class Shark {
        +locomotion: string = "swim"
    }
    class Dolphin {
        +locomotion: string = "swim"
    }
    Animal <|-- Fish
    Animal <|-- Mammal
    Fish <|-- Shark
    Mammal <|-- Dolphin</pre>
<h3>B: An underwater creature racing game</h3>
<p>On the other hand, for an underwater creature racing game, being able to handle &quot;how fast it swims&quot; might be enough. In that case, the data model would look like this.</p>
<pre class="mermaid">classDiagram
    class AquaticAnimal {
        +name: string
        +speed: int
        +stamina: int
    }
    class Shark {
        +name: string = "Shark"
        +speed: int = 80
        +stamina: int = 70
    }
    class Dolphin {
        +name: string = "Dolphin"
        +speed: int = 75
        +stamina: int = 85
    }
    AquaticAnimal <|-- Shark
    AquaticAnimal <|-- Dolphin</pre>
<p>As you can see, even when you are dealing with the same shark and dolphin, the result of data design changes depending on what kind of system you are building. And what you consolidate changes along with it.</p>
<p>In A, it is likely reasonable to factor out the shared parts along the lines of biological classification. In B, consolidating at the level of &quot;underwater creature&quot; is the natural choice.</p>
<h2>3. Assume that abstraction is hard (and can break)</h2>
<p>Abstraction is not the kind of thing where you can always arrive at the right answer easily. If you widen your view a little, even the concepts we take for granted took a long time to be established.</p>
<p>For example, the act of &quot;counting&quot; must have existed since ancient times, but generalizing and systematizing the concept of &quot;number&quot; took a long time. It is also said that counterintuitive concepts such as negative numbers took time before they were widely accepted. (Reference: <a href="https://en.wikipedia.org/wiki/Timeline_of_mathematics">Timeline of mathematics</a>)</p>
<p>The lesson to draw from this is as follows.</p>
<ul>
<li>Abstraction is inherently hard</li>
<li>It can take time before &quot;what can truly be consolidated&quot; becomes visible</li>
<li>Duplication in the code arising in the meantime is, to some extent, natural</li>
</ul>
<p>And in software, there is a factor that makes it even harder: premises change.</p>
<h3>3.1 Changing premises break abstractions</h3>
<p>For example, suppose there is a system that manages the selling price of products. If you design it on the premise that the tax rate is uniform, it might take the following shape.</p>
<pre class="mermaid">classDiagram
    class Product {
        +name: string
        +priceExcludingTax: decimal
        +sellingPrice(): decimal
    }</pre>
<p>At this point, you would be tempted to implement <code>sellingPrice()</code> as shared logic, something like &quot;price before tax × 1.10.&quot;</p>
<p>But if the premise later changes to &quot;the tax rate differs by product type,&quot; as with the introduction of a reduced tax rate, that simple shared implementation breaks down.</p>
<pre class="mermaid">classDiagram
    class Product {
        +name: string
        +priceExcludingTax: decimal
        +taxCategory: TaxCategory
        +sellingPrice(): decimal
    }

    class TaxCategory {
        +taxRate: decimal
    }</pre>
<p>What matters here is not <em>baking the tax rate into the class</em>, as in &quot;food is 1.08, appliances are 1.10,&quot; but rather treating <strong>the tax rate as a &quot;rule that can change&quot;</strong> and giving it its own place in the design (it may also vary by period or by country).</p>
<p>Going one step further, the following perspective is the core of abstraction.</p>
<ul>
<li>The &quot;number&quot; that is the tax rate is variable</li>
<li>The &quot;rule&quot; of calculating the tax amount (the fact that a calculation exists) is close to invariant</li>
</ul>
<p>Abstraction is the work of determining the boundary between this &quot;invariant&quot; and &quot;variable.&quot;<br>And future changes in premises can shift that boundary. I believe this is the biggest reason abstraction is hard.</p>
<h2>4. How to take on abstraction</h2>
<p>That said, if we give up on abstraction, the code will keep growing full of duplication, and it will eventually become hard to maintain. So we need to take on abstraction within a realistic scope.</p>
<p>There are two approaches I consider effective.</p>
<ul>
<li>Clarify the product concept, and consider a wide range of possible future developments</li>
<li>Research similar cases thoroughly</li>
</ul>
<h3>4.1 Clarify the product concept, and consider a wide range of possible future developments</h3>
<p>When considering abstraction, the most important thing is the product concept.</p>
<p>For example, if the concept is &quot;a system that manages the tax amount of all products and streamlines company-wide operations,&quot; then</p>
<ul>
<li>generality that holds up across all products handled company-wide</li>
<li>resilience to change on the premise of operation over years (tax law revisions, and so on)</li>
</ul>
<p>are likely to be required.</p>
<p>You cannot fully predict how tax law will be revised, but you can research &quot;what patterns are possible.&quot; You need to look at the tax systems of various countries and regions and the trends of past revisions, grasp the range of changes that could occur, and then decide &quot;how much of that range to include in the design as requirements.&quot;</p>
<p>On the other hand, if the concept is &quot;a system that, for now, manages the tax amount of the products my own team handles,&quot; then</p>
<ul>
<li>a design that limits the scope of target products</li>
<li>pragmatic simplifications leaning on near-term operational premises (e.g., a fixed 10% tax rate)</li>
</ul>
<p>can also be reasonable.</p>
<p>In this way, data design changes greatly depending on the product concept, and as a result the direction of abstraction changes too.</p>
<h3>4.2 Research similar cases thoroughly</h3>
<p>Abstraction is hard. That is exactly why it is realistic to assume that the probability of deriving a near-best answer from your own experience and knowledge alone is not high.</p>
<p>So I recommend researching what data designs similar software adopted, and why.</p>
<ul>
<li>The design philosophy of existing products</li>
<li>Published design documents and case studies</li>
<li>Model designs and discussions in OSS (issues, PRs, and so on)</li>
</ul>
<p>From this kind of information, you can take in &quot;the process by which multiple people wrestled with the problem&quot; and still make the final call in a way that fits your own context. As a result, I believe your chances of reaching a sound answer are higher than thinking alone.</p>
<h2>5. What to do when you find code that looks consolidatable</h2>
<p>From an implementer&#39;s standpoint, I understand the urge to consolidate when you find code that is clearly the same. In such cases, I recommend considering the following.</p>
<ul>
<li>Ask yourself, from a data-design perspective, whether these two pieces of code are essentially the same concept (a review, an outside perspective, a discussion, or bouncing ideas off an LLM all work)</li>
<li>Even if you do consolidate for now, design it so that you can dismantle it quickly if it turns out to be wrong (keep the callers&#39; dependencies thin, create boundaries, and so on)</li>
</ul>
<p>In addition, having &quot;rules of thumb&quot; for the decision makes it easier to avoid getting stuck.</p>
<ul>
<li><strong>When in doubt, tolerate duplication (Avoid Hasty Abstraction)</strong></li>
<li><strong>Only once the same thing appears in three places should you start considering consolidation (Three Strikes Rule)</strong></li>
</ul>
<p>The value of consolidation is not that it lets you write less.<br>You need to think about how far the coupling cost created by consolidation will make itself felt in the future.</p>
<p>The claim of this article is not &quot;never consolidate when you find shared code.&quot;<br>It is simply &quot;judge consolidation by conceptual identity, not by how the code looks.&quot;</p>
<p>I also believe that a major cause of technical debt lies in flaws in data design (domain design). That is exactly why, if you have the habit of going back to data design when you find &quot;shared code,&quot; you may be able to slow the accumulation of debt.</p>
<h2>Summary</h2>
<ul>
<li>Consolidation is not a coding technique; it is a data-design decision (treating concepts as the same, and separating them)</li>
<li>Abstraction is hard, and it can &quot;break&quot; in the future when premises change</li>
<li>The core of abstraction lies in determining the boundary between &quot;invariant&quot; and &quot;variable&quot;</li>
<li>Tolerating duplication when in doubt, and considering consolidation once it appears in three places, is about the right balance</li>
</ul>
<p>Rather than immediately implementing a shared function just because you found shared code, you also have the option of going back to data design and thinking it through. I would be glad if this article becomes an occasion for that.</p>
<hr>
<p>At <a href="https://herp.careers/v1/flyle">Flyle, Inc.</a>, where I serve as VP of Technology, we are currently hiring software engineers.<br>If this article resonated with you, if you are curious about how we bring these ideas into actual practice, or if you are interested in our business, let us talk in a casual interview.</p>
]]></content:encoded>
		</item>
		<item>
			<title>What It Takes to Be a Tech Lead</title>
			<link>https://blog.baseballyama.com/posts/20260121-tech-lead</link>
			<guid>https://blog.baseballyama.com/posts/20260121-tech-lead</guid>
			<pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>At [Flyle](https://flyle.io/jp), where I work, I have been involved in technical decision-making as a tech lead for a l</description>
			<content:encoded><![CDATA[<p>At <a href="https://flyle.io/jp">Flyle</a>, where I work, I have been involved in technical decision-making as a tech lead for a long time.</p>
<p>Going forward (or in the near future), there will be more occasions to split up the role or to grow the next person who takes it on. So today, I want to lay out what I think a tech lead needs.</p>
<h2>1. Working Backward from Business Strategy</h2>
<p>In a for-profit company, product development is not an end in itself; it is a means of driving business growth. Technology selection therefore has to be a decision that aligns with business strategy, not with personal taste or what is currently in fashion.</p>
<p>That requires the ability to use tools like SWOT analysis to lay out your company&#39;s strengths, weaknesses, opportunities, and threats, and then translate that into criteria you can actually judge technology choices against.</p>
<p>At Flyle, for example, we defined the competitive advantage of our new product as &quot;being able to analyze large volumes of data quickly.&quot; The important part here is not letting the strength stay an abstract slogan, but defining it in a form you can use for design and investment decisions.</p>
<p>Concretely, we defined &quot;large volumes of data&quot; as X0,000 records per tenant, and &quot;quickly&quot; as aggregation completing within roughly X seconds (the actual numbers are not public). This put technology selection in a state where it could be evaluated against performance requirements we had to meet, rather than a vague sense of &quot;fast enough.&quot;</p>
<p>We were also, at the time, a startup in a growth phase, in a financial and organizational position where we could invest in a foundation that would strengthen our advantage over the medium to long term, even if it carried some up-front cost. In other words, we were at a phase where we could judge not only &quot;performance in the ideal case&quot; but also the practical question of &quot;is this achievable with the company&#39;s current capacity.&quot;</p>
<p>With that as the premise, we compared several OLAP options. Snowflake and BigQuery were ruled out on cost. Our analytical queries tend to get complex, and our testing showed that the cost per query execution could run higher than expected. Since this is something we ship as a product, the more usage grows, the more queries get executed. We concluded that, given our situation at the time, it was likely to become too expensive.</p>
<p>Redshift was ruled out on performance. It would have been difficult to meet our performance requirements (aggregating data on the order of X0,000 records per tenant within roughly X seconds), so it could not guarantee the product&#39;s advantage.</p>
<p>The result of that comparison was that, at least under the assumptions we had then, ClickHouse was the strongest option for realistically meeting the performance requirements. However, many of our customers are enterprises with strict security requirements. That ruled out ClickHouse Cloud, so we went with self-hosting.</p>
<p>Running it ourselves means that, once you account for availability and redundancy, you need at least several compute nodes, and the infrastructure cost is far from trivial. ClickHouse also has a lot of tuning knobs, which raises the operational burden. In other words, this choice was a decision to knowingly take on cost and operational difficulty in exchange for the benefit of high performance.</p>
<p>We adopted ClickHouse anyway because meeting the performance requirements we had defined <em>was</em> the new product&#39;s competitive advantage. Not &quot;because it&#39;s popular&quot; or &quot;because it&#39;s fast,&quot; but being able to speak with accountability to the question &quot;is this cost justified in order to secure the business&#39;s competitive advantage&quot; — that, I think, is the first thing a tech lead needs when choosing technology.</p>
<h2>2. Overwhelming Ownership</h2>
<p>A tech lead&#39;s role is not only to make technical decisions. It is to stay responsible, to the end, for the results those decisions produce.</p>
<p>For example, if the business strategy has led us to promise a customer a delivery date for a feature, the tech lead has to drive the team to completion so that promise is kept. And when a critical bug that shakes the foundation of the business occurs, such as data loss, they need to stay with it until the situation is resolved.</p>
<p>This does not necessarily mean staying on the front line of the work yourself. What matters more is leading the team to the goal of &quot;solving the problem&quot; by whatever means necessary, including reallocating team resources, negotiating scope reductions, revisiting priorities, or putting in an unglamorous stopgap fix.</p>
<h2>3. Technical Dialogue</h2>
<p>A tech lead serves as a technical compass for the team. That does not mean being the most knowledgeable person in every technical area. If anything, given how complex modern stacks have become, it is not realistic for one person to cover every area at production level.</p>
<p>What matters instead is the ability to draw out the knowledge of people who know more than you do, and turn it into the best decision.</p>
<p>In my case, for example, I have depth in the application layer (DB, backend, frontend), but limited hands-on experience building infrastructure. That does not mean I hand off infrastructure design and walk away. I catch up on it myself first, and go into the discussion with people who are strong in infrastructure holding a hypothesis and a design proposal. My style is to pose questions and sharpen my resolution on the topic by learning through the discussion.</p>
<p>There was also a case where we adopted a temporal data model for a feature. I understood the concept, but had no practical experience with it, so it was not among my initial options. A member with more experience proposed it, and I was able to conclude it was the best fit for the requirements. Decisions like that are hard to reach on individual knowledge alone.</p>
<p>The dialogue skill a tech lead needs is not just small talk. It is doing the reading up front even on unfamiliar territory, and coming to the discussion with a hypothesis. And then drawing out the team&#39;s strengths while landing on a technical judgment the team can genuinely agree with. I think it is the accumulation of that which becomes a tech lead&#39;s value.</p>
<h2>4. A Standout Strength</h2>
<p>That said, the title &quot;tech lead&quot; presupposes being a technical leader. Taking on the role with no technical strength tends to feel off to the people around you.</p>
<p>So I would recommend building at least one strength that makes people think &quot;this area, I can leave to them.&quot; Strength counts for more with depth than with breadth.</p>
<p>In my case, I work as a core team member of the UI framework Svelte, and have relatively deep knowledge of compilers, parsers, and static analysis — the language-toolchain space. On top of that, at Fujitsu, where I joined as a new graduate, I spent a long time working on development standardization for a 300-person project.</p>
<p>Combining those two, I feel my strength is in getting the mechanics of development in order. For instance, when the same point keeps coming up in code review, I add a static analysis rule (writing my own when necessary) and turn it into a mechanism that stops the same comment from recurring, which cuts down review effort.</p>
<p>Likewise, when I felt testing the logic layer on the frontend was a problem, I implemented a custom compiler that extracts just the logic from Svelte components, giving us a foundation for writing tests focused on that logic.</p>
<p>I see these efforts as the product of two things: experience with development standardization made it easier to spot the bottlenecks dragging down team productivity, and knowledge of language toolchains made it possible to turn that into something effective.</p>
<p>A strength does not have to be a flashy accomplishment. Consistently producing value in a specific area and continuing to give it back to the team is what builds credibility and trust as a tech lead.</p>
<h2>5. Designing Authority (For Those Appointing a Tech Lead)</h2>
<p>The qualities above are largely ones the tech lead can grow through their own effort. What tends to get overlooked, though it is decisively important, is &quot;how much authority the tech lead is given.&quot;</p>
<p>The tech lead&#39;s role is to set technical direction. But if you hand them the responsibility without the decision-making authority, the team does not function.</p>
<p>For example, if they conclude &quot;we need to invest in infrastructure to meet the performance requirements&quot; but have no budget discretion, and every decision waits on approval, the tech lead cannot set direction.
If they have promised a customer &quot;we&#39;ll ship by this date&quot; but cannot move scope or reprioritize, then all the ownership in the world leaves them with no lever to pull.</p>
<p>This is not a shortfall in the person. It is a problem of organizational design.</p>
<p>That is why whoever appoints a tech lead has to answer the following questions at the moment of appointment.</p>
<ul>
<li>Who holds the final say on technology selection</li>
<li>When quality, schedule, and cost collide, who decides the priority</li>
<li>Who can decide to cut scope or delay a release</li>
<li>Who can carve out time to pay down technical debt</li>
</ul>
<p>If this stays ambiguous, the tech lead becomes someone who carries only the accountability, not someone who decides. Decisions get slower, friction increases, people burn out, and in the end the organization&#39;s velocity drops.</p>
<p>Put the other way: when the authority is clearly designed, the tech lead functions as a technical compass, and the team can move forward with confidence.</p>
<p>Appointing a tech lead also means building a structure in which decisions get made.</p>
<h2>Summary</h2>
<p>The qualities described in this article are what I think as of January 2026, and they should keep getting updated.</p>
<p>Also, since the scope of the &quot;tech lead&quot; role is not strictly defined in the first place, some readers may find parts of this off from their own experience.</p>
<p>I would be glad if you read it as one person&#39;s take.</p>
]]></content:encoded>
		</item>
	</channel>
</rss>
