<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>hopes7 Blog (English)</title>
    <link>https://hopes7.com</link>
    <description>AI, developer productivity, and tech blog</description>
    <language>en-US</language>
    <atom:link href="https://hopes7.com/feed-en.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Fri, 23 Oct 2026 18:21:55 GMT</lastBuildDate>
    
    <item>
      <title><![CDATA[Eight Compromised CA Keys and a Week of Emergency Upgrades: the eMudhra Incident]]></title>
      <link>https://hopes7.com/en/blog/emudhra-ca-key-leak-2026</link>
      <guid>https://hopes7.com/en/blog/emudhra-ca-key-leak-2026</guid>
      <description><![CDATA[A P-384 key that should carry 384 bits of security ended up with a handful of effective bits. Fixing it took every major language ecosystem, and the keyword of this incident is not 'revoke' — it's 'rebuild'.]]></description>
      <content:encoded><![CDATA[<h2 id="the-short-version">The Short Version</h2>
<p>If this one slipped past you last week, it's worth catching up on:</p>
<p>On October 17, 2026, a security researcher discovered that the private keys of eight intermediate CA certificates from eMudhra (emSign) could be derived by a third party. The keys were generated during a key ceremony on April 30, 2025; due to a flaw in the process, P-384 keys that should carry 384 bits of security ended up with only a few bits of effective entropy.</p>
<p>This is not the routine "revoke one TLS certificate" incident. Derivable private keys mean an attacker could, in theory, issue browser-trusted certificates for <strong>any domain</strong> and quietly man-in-the-middle it — and the code-signing intermediates could have lent malware a legitimate-looking signature.</p>
<p>So over the past week we watched an unusually broad, cross-ecosystem response:</p>
<ul>
<li>Chrome, Mozilla and Apple blocked the six affected TLS intermediates; Microsoft blocked the two code-signing ones;</li>
<li>Go released 1.27.3 / 1.26.10, hard-coding a deny list into <code>crypto/x509</code> (CVE-2026-111943);</li>
<li>Node.js shipped its October 21 security releases 22.23.4 / 24.22.1 / 26.12.1;</li>
<li>Python's certifi, pip and uv, Rust's rustls-webpki and webpki-roots, and PHP's composer/ca-bundle all followed;</li>
<li>Oracle issued an out-of-band security alert for Java on October 21–22 (CVE-2026-91403, CVSS 9.1);</li>
<li>Electron, curl, and the Debian/Ubuntu/Alpine CA bundles updated in step.</li>
</ul>
<p>The good news: as of Talos's analysis on October 21, there was no sign of abuse in the wild, and Chrome found no rogue certificates in Certificate Transparency logs.</p>
<p>But "not abused yet" has never been a reason to skip an upgrade — the whole point of this emergency wave is to close the door before the abuse starts.</p>
<h2 id="what-actually-happened">What Actually Happened</h2>
<p>The timeline starts about eighteen months before discovery.</p>
<p>On April 30, 2025, eMudhra held a key-generation ceremony for 24 subordinate CAs. A flaw in a wrap/unwrap step produced a set of badly weakened P-384 keys. Per Cisco Talos's analysis, their effective entropy was 3 to 8 bits — against a required 384. Eight of these weak keys ended up on eight intermediate certificates: six TLS intermediates (EV/OV/DV, G3B and G3C variants) and two code-signing ones, all under emSign Root TLS CA – G3 and Root CS CA – G3, plus a cross-signed path to Comodo AAA.</p>
<p>A few bits of entropy means the private key isn't in "unguessable" territory — it's in "brute-force it in a moment" territory. Reports note that one recovered key's value was absurdly small.</p>
<p>Those keys sat in the wild for roughly 17 months before discovery on October 17, 2026. eMudhra has since revoked the affected certificates and stopped issuing from its G3 root. Mozilla Bugzilla #2083449 tracks the handling; Talos published the full analysis on October 21.</p>
<h2 id="why-the-fix-had-to-be-so-heavy-handed">Why the Fix Had to Be So Heavy-Handed</h2>
<p>For a routine certificate incident, the fix is "revoke and replace", and applications barely notice.</p>
<p>Not this time. The private keys themselves are derivable, so revocation isn't enough — every TLS client on the planet must <strong>reject these public keys</strong>, wherever they show up.</p>
<p>The complication: the places that trust these keys go far beyond browsers.</p>
<ul>
<li>Some runtimes compile their trust store into the binary (Go's standard library, Rust's webpki-roots);</li>
<li>Some distribution channels maintain their own snapshots (Python's certifi, PHP's composer/ca-bundle, Linux distros);</li>
<li>And the JDK ships its own trust store.</li>
</ul>
<p>So every ecosystem converged on the same fix: put the SPKI SHA-256 hashes of the eight compromised certificates on a deny list and ship it in an emergency release. The Go team went as far as hard-coding the list into <code>crypto/x509</code>.</p>
<p>That's what makes the keyword of this incident <strong>rebuild</strong>.</p>
<p>Upgrading the toolchain is only step one. In an already-compiled Go binary, the old deny list — or no list at all — is still baked in. In an already-published container image, the old <code>ca-certificates</code> are still there. An Electron app already delivered to users still runs the old framework. None of these get fixed by upgrading your dev machine — they must be rebuilt and redistributed.</p>
<h2 id="fix-versions-across-ecosystems">Fix Versions Across Ecosystems</h2>
<p>As announced between October 21 and 22:</p>
<table>
<thead>
<tr>
<th>Ecosystem</th>
<th>Fixed versions</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Go</td>
<td>1.27.3 / 1.26.10 (CVE-2026-111943)</td>
<td>SPKI deny list in <code>crypto/x509</code>; <strong>compiled binaries must be rebuilt</strong></td>
</tr>
<tr>
<td>Node.js</td>
<td>22.23.4 / 24.22.1 / 26.12.1 (Oct 21 releases)</td>
<td>TLS rejects chains containing blocked public keys; bundled CA update</td>
</tr>
<tr>
<td>Python</td>
<td>certifi 2026.10.19; pip 26.2.2; uv 0.13.4</td>
<td>emSign G3 root removed from the trust store</td>
</tr>
<tr>
<td>Rust</td>
<td>rustls-webpki 0.103.16; webpki-roots 1.0.10</td>
<td>Root data compiled into binaries updated</td>
</tr>
<tr>
<td>PHP</td>
<td>composer/ca-bundle 1.6.0 / 1.4.16</td>
<td>No longer trusts the eight intermediates</td>
</tr>
<tr>
<td>Java</td>
<td>CVE-2026-91403 (OpenJDK advisory, Oct 21)</td>
<td>Upgrade the JDK, or replace <code>blocked.certs</code>, or force revocation checking</td>
</tr>
<tr>
<td>Electron</td>
<td>44.9.1 and up</td>
<td>Apps must be <strong>repackaged and redistributed</strong></td>
</tr>
<tr>
<td>curl / Linux distros</td>
<td>updated ca-certificates</td>
<td><strong>Container images need rebuilding</strong></td>
</tr>
<tr>
<td>Bun / FreeBSD</td>
<td>no fix released as of Oct 21</td>
<td>Watch for updates, then upgrade immediately</td>
</tr>
</tbody>
</table>
<h2 id="java-the-part-that-needs-more-than-a-trust-store-refresh">Java: the Part That Needs More Than a Trust-Store Refresh</h2>
<p>Java deserves its own section because the exposure and the fix are both easy to miss.</p>
<p>On October 21, OpenJDK published an advisory (CVE-2026-91403, CVSS 9.1, in <code>javax.net.ssl</code>): the JDK's bundled trust store contained the compromised eMudhra intermediates, so standalone runtimes trust any certificate chaining to them — including forged ones. The affected list is broad:</p>
<ul>
<li>JDK 27.0.1, 25.0.5, 21.0.13, 17.0.21, 11.0.32.1, 8u504, and earlier.</li>
</ul>
<p>Oracle issued an out-of-band security alert, and various CERTs started relaying it on October 22.</p>
<p>The OpenJDK advisory offers three paths, in priority order:</p>
<ol>
<li><strong>Upgrade</strong> standalone JDK installs to the fixed releases;</li>
<li>If you can't upgrade yet, replace <code>$JDK_HOME/lib/security/blocked.certs</code> with the official file (called <code>blacklisted.certs</code> on JDK 8) — back up your custom entries first;</li>
<li>As mitigation, enable revocation checking: <code>-Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true</code>, and verify signed JARs with <code>jarsigner -verify -revcheck -strict</code>.</li>
</ol>
<p>Two details worth flagging: JDKs installed from OS repositories (Arch, Debian, Fedora, RHEL, Ubuntu, etc.) use the operating system's store, so updating the system <code>ca-certificates</code> suffices — but <strong>FreeBSD and Homebrew builds must be updated directly</strong>. And if a machine has several JDKs installed, every one of them needs this.</p>
<h2 id="the-node-releases-also-contain-an-rce">The Node Releases Also Contain an RCE</h2>
<p>If you only watched the CA story, you may have missed the risk that hits Node users more directly: the October 21 Node.js security releases fixed around ten vulnerabilities, four of them High — and the heaviest one isn't about certificates at all:</p>
<p><strong>CVE-2026-111497</strong>: an out-of-bounds write in the Brotli decoder that permits remote code execution, dubbed "Node2Shell" in echo of React2Shell. All three release lines are affected. It was reported by researchers from OpenAI Codex Security.</p>
<p>Also in the same batch:</p>
<ul>
<li>two TLS certificate hostname-validation bypasses (IPs written with fullwidth digits, hosts that loosely resolve to an IP), High;</li>
<li><code>dns.resolve</code> truncating names containing a NUL character, High;</li>
<li>entropy caches of <code>crypto.randomUUID()</code> / <code>randomInt()</code> persisted into startup snapshots and SEAs — every process then repeats the same "random" values, Medium. If you generate tokens or IDs with these, that one matters;</li>
<li>a keep-alive socket processing a response written before the request, non-canonical IPv4 hostname validation, and more.</li>
</ul>
<p>Fixed versions: 22.23.4 / 24.22.1 / 26.12.1 — upgrade within your major line. Every EOL version remains affected; there is no lucky exemption.</p>
<h2 id="what-you-should-do-now">What You Should Do Now</h2>
<p>Match your stack:</p>
<p><strong>Everyone:</strong></p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Upgrade local and CI toolchains (Node example)</span>
nvm install 24.22.1 &#x26;&#x26; nvm <span class="hljs-built_in">alias</span> default 24.22.1
</code></pre>
<p>Then push your <strong>artifacts</strong> through the pipeline again: recompile, repackage, redeploy. For container images, pull the fresh base image before building — don't hit stale layer caches.</p>
<p><strong>Go:</strong> after upgrading to 1.27.3 / 1.26.10, rebuild with <code>go build</code> and redeploy every service. Updating the Go version in CI without recompiling and redeploying fixes nothing.</p>
<p><strong>Python:</strong> upgrade pip 26.2.2, certifi 2026.10.19, uv 0.13.4 — in CI and containers too.</p>
<p><strong>Rust:</strong> <code>cargo update -p rustls-webpki -p webpki-roots</code>, then re-release.</p>
<p><strong>PHP:</strong> <code>composer update composer/ca-bundle</code>.</p>
<p><strong>Java:</strong> follow the OpenJDK advisory — upgrade or swap <code>blocked.certs</code>; count how many JDKs you have.</p>
<p><strong>Electron apps:</strong> upgrade the framework, repackage, redistribute — installed old builds are not protected by the new release.</p>
<p><strong>Not yet fixed:</strong> Bun and FreeBSD hadn't shipped fixes as of October 21. Watch their channels and upgrade the moment they do.</p>
<h2 id="what-this-site-did">What This Site Did</h2>
<p>The day I wrote this post, we closed the doors around here too:</p>
<ul>
<li>The local dev machine's Node went from 24.14.1 (affected) to <strong>24.22.1</strong> (fixed);</li>
<li>The repo's <code>.nvmrc</code> moved from 22.22.2 to <strong>22.23.4</strong>, with <code>engines.node</code> bumped to <code>>=22.23.4</code>, so everyone who clones this repo gets a patched toolchain;</li>
<li><code>pnpm install</code>, <code>pnpm lint</code>, <code>pnpm typecheck</code> and <code>pnpm build</code> all pass on the new toolchain.</li>
</ul>
<p>As for runtime, this site runs on Cloudflare Workers: outbound TLS trust is maintained platform-side and doesn't depend on this machine's ca-certificates. But the local dev and build environments still needed the upgrade — an artifact's trust boundary starts at the machine that builds it.</p>
<p>The most tangible takeaway from doing this: none of the usual tooling sees this layer. <code>pnpm audit</code> and friends only look at what's in package.json — not your toolchain, your base images, or your JDK.</p>
<h2 id="takeaways">Takeaways</h2>
<p>Three things I want to remember from this one.</p>
<p>First, <strong>the foundation of digital trust is entropy and process</strong>. A strength of 384 bits collapsed to 3–8 because of one ceremony flaw — a difference of many orders of magnitude — and the error hid for 17 months. In PKI, "the randomness wasn't random enough" is not bad luck; it's a single point of failure that ceremonies, audits and verification exist to defend.</p>
<p>Second, <strong>the unit of remediation has shifted from "upgrade" to "rebuild"</strong>. We used to say "upgrade to fix", implying a change of state somewhere. This time, toolchains, binaries, container images and shipped apps each needed an explicit rebuild. The deeper the supply chain, the longer the rebuild list.</p>
<p>Third, <strong>don't outsource security awareness to a single tool</strong>. Auditors and bots watch the thin application-dependency layer; CA stores, language toolchains and system certificate packages below that still depend on humans reading the advisories. The ecosystem's response speed this time — from discovery on October 17 to fixes across the board within a week — was genuinely good. But it only helps if you see the announcement.</p>
<p>Upgrade now, while the weak keys are still unused.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://blog.talosintelligence.com/compromised-intermediate-certificates">Cisco Talos: Compromised eMudhra intermediate certificates</a></li>
<li><a href="https://nodejs.org/en/blog/vulnerability/october-2026-security-releases">Node.js: Wednesday, October 21, 2026 Security Releases</a></li>
<li><a href="https://openjdk.org/groups/vulnerability/advisories/2026-10-21">OpenJDK Vulnerability Advisory (CVE-2026-91403), 2026-10-21</a></li>
<li><a href="https://www.oracle.com/security-alerts/alert-cve-2026-91403.html">Oracle Security Alert Advisory for CVE-2026-91403</a></li>
<li><a href="https://thehackernews.com/2026/10/eight-emudhra-ca-keys-compromised.html">The Hacker News: Eight eMudhra CA Keys Compromised</a></li>
<li><a href="https://blog.openvpn.net/eight-compromised-certificate-authority-keys-what-happened-and-what-to-update-now">OpenVPN: What happened and what to update now</a></li>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2083449">Mozilla Bugzilla #2083449</a></li>
</ul>]]></content:encoded>
      <pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate>
      <category>tls</category>
      <category>pki</category>
      <category>ca</category>
      <category>emudhra</category>
      <category>nodejs</category>
      <category>go</category>
      <category>java</category>
      <category>supply-chain-security</category>
    </item>
    <item>
      <title><![CDATA[When CSP Monitoring Met InfluxDB: A 7.5 Billion Series Disaster]]></title>
      <link>https://hopes7.com/en/blog/influxdb-cardinality-explosion-csp-monitoring</link>
      <guid>https://hopes7.com/en/blog/influxdb-cardinality-explosion-csp-monitoring</guid>
      <description><![CDATA[Building a front-end security monitoring system for a SaaS platform with ~10M MAU. InfluxDB seemed like the right call. Test passed in 3 days. Production exploded in 20 minutes. The root cause? Using a time-series database as a search engine.]]></description>
      <content:encoded><![CDATA[<h2 id="how-it-started">How It Started</h2>
<p>A while back, we wanted to build a front-end security monitoring system:</p>
<ul>
<li>CSP (Content Security Policy) violation tracking</li>
<li>First-party JS integrity verification</li>
<li>Third-party JS hash checks</li>
<li>Risky script alerts</li>
</ul>
<p>The product is a SaaS platform serving multiple countries. Combined monthly active users across all sites: close to 10 million.</p>
<p>The requirements sounded straightforward. Every time a user opens a page, check CSP compliance, verify JS hashes, and report anomalies. Then build trend analysis, risk statistics, real-time monitoring, and incident search on top.</p>
<p>We picked InfluxDB. The reasoning was, well...</p>
<p>"Time-series databases are fast."</p>
<p>Honestly, I wasn't very familiar with time-series databases at the time. But AI-assisted development moves so fast these days—the architecture isn't even figured out yet, and the system is already running.</p>
<p>The entire system was almost entirely written by Codex. Data structures, InfluxDB writes, dashboards, aggregation queries, front-end hash verification, query APIs—all AI-generated.</p>
<p>First version. Three days to production. Staging environment looked perfect.</p>
<p>Then we deployed.</p>
<p>Everything exploded.</p>
<h2 id="v1-the-memory-aggregation-disaster">v1: The Memory Aggregation Disaster</h2>
<p>At first, I thought the queries were just badly written.</p>
<p>To be fair, the v1 logic was brutally simple: pull all raw data out of InfluxDB, then aggregate in Node.js memory.</p>
<p>Staging was completely fine. Small data volume, few users, low cardinality.</p>
<p>Production was a different planet.</p>
<p>About 20 minutes after deployment, alerts started firing:</p>
<ul>
<li>Node memory spiked</li>
<li>CPU went through the roof</li>
<li>Dashboard froze</li>
<li>Queries kept getting slower</li>
</ul>
<p>Turned out the bottleneck wasn't InfluxDB at all. It was me pulling every raw record into Node and crunching it in memory.</p>
<p>So we immediately started on v2.</p>
<h2 id="v2-still-broken">v2: Still Broken</h2>
<p>This time we made several changes:</p>
<ul>
<li>Pushed aggregation logic into InfluxDB's native functions</li>
<li>Minimized Node-side computation</li>
<li>Optimized raw query patterns</li>
<li>Rewrote the query layer</li>
</ul>
<p>Still broken.</p>
<p>And this time it was weirder—some queries took several minutes to return.</p>
<p>At this point I started wondering: is InfluxDB just not good enough?</p>
<p>Then it hit me. The problem wasn't InfluxDB. The problem was:</p>
<p><strong>I was using a time-series database as a high-dimensional search engine.</strong></p>
<h2 id="what-time-series-databases-actually-do">What Time-Series Databases Actually Do</h2>
<p>A common misconception when people first encounter InfluxDB, Prometheus, or OpenTSDB: "It's a database, so I should be able to query it like MySQL, right?"</p>
<p>No. What time-series databases actually solve is:</p>
<blockquote>
<p>How does a metric change over time?</p>
</blockquote>
<p>They excel at:</p>
<ul>
<li>CPU usage trends</li>
<li>Error rate tracking</li>
<li>QPS fluctuations</li>
<li>Time-bucket aggregation</li>
<li>Metric-based alerting</li>
</ul>
<p>That's <strong>Time Series Analysis</strong>.</p>
<p>They do <strong>not</strong> excel at:</p>
<ul>
<li>Arbitrary multi-condition filtering</li>
<li>Multi-dimensional search</li>
<li>Pagination</li>
<li>Looking up individual records</li>
</ul>
<p>When I re-examined my actual requirements, I realized what I really needed was:</p>
<ul>
<li>Search by userId</li>
<li>Search by scriptUrl</li>
<li>Search by hash</li>
<li>Search by pageUrl</li>
<li>Multi-condition combination filters</li>
<li>Risk investigation</li>
<li>Pagination</li>
<li>Detailed record retrieval</li>
</ul>
<p>This isn't "time-series analysis." This is a <strong>risk control system</strong>, a <strong>log search engine</strong>, an <strong>OLTP query system</strong>.</p>
<h2 id="the-abyss-series-cardinality">The Abyss: Series Cardinality</h2>
<p>The real problem emerged when I finally understood what <code>tag</code> and <code>field</code> actually mean in InfluxDB.</p>
<p>At first I thought:</p>
<ul>
<li>tag = indexed</li>
<li>field = regular column</li>
</ul>
<p>Nope. That's not it at all.</p>
<p>InfluxDB tags aren't just "indexed fields"—they're part of the <strong>series identity</strong>. When data is written, InfluxDB has already determined the series structure.</p>
<p>This is a completely different universe from MySQL. In MySQL, no index today? Just run <code>ALTER TABLE ADD INDEX</code> tomorrow. InfluxDB doesn't work like that. A wrong tag design means backfill, rewrite, rebuild—basically rebuilding the entire time-series structure.</p>
<h3 id="75-billion-series">7.5 Billion Series</h3>
<p>Then I started researching what <strong>series cardinality</strong> actually means.</p>
<p>InfluxDB combines <code>measurement + tag set</code> into a unique series.</p>
<p>For example:</p>
<pre><code>measurement=csp_report
siteCode=us
env=prod
userId=123
scriptUrl=a.js
pageUrl=/checkout
hash=xxxx
</code></pre>
<p>This entire combination is one series.</p>
<p>Change the <code>userId</code>? New series. Change the <code>hash</code>? Another new series.</p>
<p>If you know InfluxDB, you're probably already laughing at seeing <code>userId</code> and <code>hash</code> as tags.</p>
<p>Then I did the math:</p>
<table>
<thead>
<tr>
<th>Dimension</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>siteCode</td>
<td>30</td>
</tr>
<tr>
<td>userId</td>
<td>100,000</td>
</tr>
<tr>
<td>hash</td>
<td>500</td>
</tr>
<tr>
<td>scriptUrl</td>
<td>50</td>
</tr>
<tr>
<td>pageUrl</td>
<td>100</td>
</tr>
</tbody>
</table>
<p>Theoretical series:</p>
<pre><code>30 × 100,000 × 500 × 50 × 100 = 7,500,000,000
</code></pre>
<p>7.5 billion.</p>
<p>The first time I calculated this, I thought I'd made a mistake. I double-checked. Triple-checked. Nope. 7.5 billion series.</p>
<p>That's when it hit me: the scariest thing about InfluxDB isn't one huge dimension—it's multiple "reasonable-looking" dimensions combining into a <strong>combinatorial explosion</strong>.</p>
<h2 id="the-real-cost-metadata-not-data">The Real Cost: Metadata, Not Data</h2>
<p>Here's the thing: we only had about 1.7 million rows per day.</p>
<p>Honestly, that's not even that much. MySQL with sharding, ClickHouse, Elasticsearch—they could all handle it.</p>
<p>But InfluxDB's most expensive resource was never data volume. It was <strong>series cardinality</strong>.</p>
<p>People often assume series ≈ data volume. It doesn't.</p>
<p>Series is more like <strong>index space</strong>. For every single series, InfluxDB:</p>
<ul>
<li>Allocates a series ID</li>
<li>Builds an inverted index</li>
<li>Maintains metadata</li>
<li>Creates tag indexes</li>
</ul>
<p>The real resource drain isn't the data itself—it's the series metadata.</p>
<p>This is why InfluxDB's official docs keep saying: don't put userId, hash, uuid, requestId, or random strings into tags. These fields will blow up your series cardinality.</p>
<h2 id="why-queries-got-progressively-slower">Why Queries Got Progressively Slower</h2>
<p>Under high cardinality, InfluxDB queries aren't just "scanning data." They're <strong>scanning massive amounts of series metadata</strong>.</p>
<p>Especially:</p>
<ul>
<li>tag filters</li>
<li>GROUP BY operations</li>
<li>series expansion</li>
</ul>
<p>These trigger:</p>
<ul>
<li>Expensive index scans</li>
<li>Cross-shard traversal</li>
<li>Frequent mmap page faults</li>
</ul>
<p>This isn't something you can fix by "optimizing queries." It's a fundamental architecture mismatch.</p>
<h2 id="the-fix-influxdb--mysql-dual-storage">The Fix: InfluxDB + MySQL Dual Storage</h2>
<p>We tore the whole system apart and rebuilt it.</p>
<p>InfluxDB stayed, but only for what it's good at:</p>
<ul>
<li>Trend analysis</li>
<li>count / sum aggregation</li>
<li>TOP N rankings</li>
<li>Time-bucket aggregation</li>
<li>Alert trends</li>
</ul>
<p>MySQL handled the search:</p>
<ul>
<li>Detail queries</li>
<li>userId lookups</li>
<li>hash lookups</li>
<li>Multi-condition filtering</li>
<li>Risk investigation</li>
<li>Pagination</li>
</ul>
<p>In other words:</p>
<p><strong>InfluxDB for trends. MySQL for records.</strong></p>
<h3 id="the-final-tag--field-design">The Final Tag / Field Design</h3>
<p>The redesigned InfluxDB measurement only kept low-cardinality tags:</p>
<pre><code># Tags (low cardinality, part of series identity)
siteCode    → ~30 values
env         → prod / staging / dev
type        → csp_violation / js_integrity / third_party_hash / risk_script

# Fields (high cardinality, NOT part of series identity)
reportCount → integer (for aggregation)
riskLevel   → string (high / medium / low)
</code></pre>
<p>All high-cardinality fields (userId, hash, scriptUrl, pageUrl) go to MySQL only. InfluxDB only cares about "how many anomalies of type X happened today, and what's the trend."</p>
<p>On the write side, we do dual writes: each record goes to both InfluxDB (for aggregation) and MySQL (for detail queries). Write consistency is handled at the application layer with retries + MySQL transactions. Missing a few writes to InfluxDB doesn't significantly affect trend accuracy.</p>
<p>Here's what I finally understood: to decide whether a system fits a time-series database, don't ask "does the data have a timestamp?" Ask:</p>
<p><strong>Are you looking at trends, or looking up records?</strong></p>
<p>These are fundamentally different problems.</p>
<h2 id="looking-back">Looking Back</h2>
<p>This whole project feels like a microcosm of software development in the AI era.</p>
<p>Codex helped me ship the entire system in 3 days. Staging passed instantly. Development velocity was insane.</p>
<p>But it didn't warn me about:</p>
<pre><code>30 × 100,000 × 500 × 50 × 100 = 7,500,000,000
</code></pre>
<p>AI can accelerate development enormously. But systems still have to obey the laws of their underlying architecture.</p>
<p><strong>Speed doesn't matter if you're heading in the wrong direction.</strong></p>]]></content:encoded>
      <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
      <category>InfluxDB</category>
      <category>time-series database</category>
      <category>CSP</category>
      <category>architecture</category>
      <category>cardinality</category>
      <category>SaaS</category>
    </item>
    <item>
      <title><![CDATA[npm Package Poisoning Is Getting More Frequent: What the TanStack Incident Teaches Us]]></title>
      <link>https://hopes7.com/en/blog/npm-supply-chain-poisoning-tanstack-2026</link>
      <guid>https://hopes7.com/en/blog/npm-supply-chain-poisoning-tanstack-2026</guid>
      <description><![CDATA[The painful part of the TanStack incident was not a stolen npm token. It was how several reasonable pieces of CI/CD trust were chained into a publish path.]]></description>
      <content:encoded><![CDATA[<h2 id="the-short-version">The Short Version</h2>
<p>npm package poisoning no longer looks like someone registering a typo-squatted package and waiting for a tired developer to mistype a name.</p>
<p>On May 11, 2026, 84 malicious versions were published across 42 <code>@tanstack/*</code> packages. According to Tanner Linsley's postmortem, the publishes happened between 19:20 and 19:26 UTC, and the incident was publicly reported by an external researcher roughly 20 minutes later.</p>
<p>The important part is what did not happen: this was not a classic stolen npm account story, and there is no evidence that npm credentials were stolen.</p>
<p>Instead, the attacker chained three things together:</p>
<ol>
<li>the <code>pull_request_target</code> "Pwn Request" pattern;</li>
<li>GitHub Actions cache poisoning across the fork-to-base trust boundary;</li>
<li>runtime extraction of an OIDC token from the GitHub Actions runner process, then direct publish requests to the npm registry.</li>
</ol>
<p>In other words, the attacker did not steal a long-lived npm token. They borrowed a legitimate publishing identity from the release environment.</p>
<p>That is why this incident feels different. Supply-chain attacks are moving from "steal the credential" toward "abuse the automation that is allowed to mint one."</p>
<h2 id="this-is-not-just-a-tanstack-problem">This Is Not Just a TanStack Problem</h2>
<p>It would be easy to turn this into a neat little morality play about one project having one bad workflow.</p>
<p>I do not think that is the useful lesson.</p>
<p>TanStack is mature, widely used, and maintained by people who understand modern JavaScript infrastructure better than most teams do. That is exactly why the incident matters. The attacker did not take the most obvious path. They looked for the joints between CI/CD, caching, OIDC, and npm trusted publishing.</p>
<p>Many teams have a version of this setup today:</p>
<ul>
<li>fork PRs that automatically run checks;</li>
<li>CI caches for pnpm, npm, or Yarn;</li>
<li>release workflows with elevated permissions;</li>
<li>npm trusted publishing through GitHub OIDC;</li>
<li>dependency installs that allow lifecycle scripts;</li>
<li>maintainers responsible for many packages at once.</li>
</ul>
<p>Each of those choices can be reasonable on its own.</p>
<p>The problem is that supply-chain attacks are rarely polite enough to stay inside one box. They connect the boxes.</p>
<h2 id="the-three-parts-worth-remembering">The Three Parts Worth Remembering</h2>
<h3 id="1-pull_request_target-is-not-the-whole-problem">1. <code>pull_request_target</code> Is Not the Whole Problem</h3>
<p><code>pull_request_target</code> is not inherently evil. It is often used for labeling PRs, commenting on issues, and updating metadata because it runs in the context of the base repository.</p>
<p>The dangerous move is different: checking out fork-controlled code inside a <code>pull_request_target</code> workflow and then executing it.</p>
<p>That crosses a boundary. The workflow is running with base-repository context, but the code being executed came from an untrusted fork. GitHub Security Lab has warned about this pattern for years under the name "pwn request."</p>
<p>In the TanStack incident, the workflow attempted to split trusted and untrusted work. That instinct was right. The missed surface was the cache.</p>
<p>It is tempting to believe this is enough:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
</code></pre>
<p>But <code>actions/cache</code> post-job saves are not simply controlled by the normal <code>GITHUB_TOKEN</code> permission block. A job that looks read-only can still mutate cache state inside the repository's cache scope.</p>
<p>That is a subtle failure mode, and subtle failure modes are where automation gets expensive.</p>
<h3 id="2-cache-is-a-performance-feature-and-a-trust-boundary">2. Cache Is a Performance Feature and a Trust Boundary</h3>
<p>We usually talk about cache as a speed optimization. That framing is too small.</p>
<p>In a modern frontend project, a package-manager cache may contain:</p>
<ul>
<li>package store data;</li>
<li>compiled artifacts;</li>
<li>native binaries;</li>
<li>files generated by <code>postinstall</code> or <code>prepare</code>;</li>
<li>dependency content later reused by release workflows.</li>
</ul>
<p>The attack took advantage of that. A malicious PR polluted the pnpm store and saved it under a key that the release workflow would later restore. When a legitimate push to <code>main</code> triggered the release workflow, the poisoned cache crossed from untrusted PR execution into a trusted publishing environment.</p>
<p>The PR did not need to be merged.</p>
<p>It could be force-pushed back to a no-op, closed, and deleted. The cache still existed.</p>
<p>That is the part I keep coming back to. Caches can outlive the social signal that made us stop looking at the PR.</p>
<h3 id="3-oidc-is-not-a-magic-shield">3. OIDC Is Not a Magic Shield</h3>
<p>npm trusted publishing and GitHub OIDC are good ideas. Short-lived credentials are better than long-lived npm tokens sitting in CI secrets. Provenance is better than a hand-rolled publish step with no paper trail.</p>
<p>But OIDC solves a specific problem: reducing long-lived secret exposure. It does not make a compromised runner safe.</p>
<p>If a release workflow has <code>id-token: write</code>, and attacker-controlled code is already executing on that runner, the attacker can try to obtain a publish-capable identity from the runtime environment. In the TanStack incident, the normal "Publish Packages" step was skipped because tests failed. The malicious code still made direct publish requests to <code>registry.npmjs.org</code>.</p>
<p>That is the uncomfortable lesson.</p>
<p>We used to ask, "Was the npm token leaked?"</p>
<p>Now we also need to ask, "Can any code path in this workflow reach the publishing identity?"</p>
<h2 id="why-npm-poisoning-keeps-showing-up">Why npm Poisoning Keeps Showing Up</h2>
<p>npm is not uniquely doomed. But the ecosystem has several properties attackers love.</p>
<p>First, dependency chains are long. One compromised package can spread through transitive dependencies into projects that never selected it directly.</p>
<p>Second, install time can execute code. <code>preinstall</code>, <code>postinstall</code>, and <code>prepare</code> scripts have legitimate uses, from native addons to generated clients to binary downloads. That legitimacy makes them hard to ban outright.</p>
<p>Third, maintainers are overloaded. A single maintainer may own dozens of packages. A single compromised publishing path can become a multiplier.</p>
<p>Fourth, CI/CD has become complicated. Release systems now involve cache layers, matrix builds, preview jobs, automatic releases, provenance, OIDC, comment bots, and third-party actions. Every layer improves productivity. Every layer also creates a new trust boundary.</p>
<p>Fifth, open source is transparent. That transparency is a strength, but it also lets attackers study workflows, cache keys, release cadence, and maintainer habits in public.</p>
<p>So no, the lesson is not "npm is unsafe" or "open source is unsafe."</p>
<p>The better lesson is this: open-source package infrastructure has become high-value production infrastructure, while many of our habits still assume the main thing to protect is a static token.</p>
<p>That mental model is no longer enough.</p>
<h2 id="what-regular-developers-can-do">What Regular Developers Can Do</h2>
<p>If you installed one of the affected TanStack versions on May 11, 2026, the official recommendation was to treat the install host as potentially compromised and rotate reachable AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials.</p>
<p>That sounds severe, but it matches the payload's goal: harvesting credentials from the environment where install scripts run.</p>
<p>For day-to-day work, I would turn a few practices into muscle memory.</p>
<h3 id="1-pay-attention-to-strange-version-bursts">1. Pay Attention to Strange Version Bursts</h3>
<p>If a package suddenly publishes several patch versions in quick succession, especially without clear release notes, pause for a minute.</p>
<p>You can do a quick check with:</p>
<pre><code class="hljs language-bash">npm view @tanstack/history versions --json
npm diff --diff=@tanstack/history@1.161.8 --diff=@tanstack/history@1.161.9
</code></pre>
<p>Manual review will not catch every malicious payload. But it can catch strange files, strange dependencies, and unexpected lifecycle scripts.</p>
<h3 id="2-treat-the-lockfile-as-a-security-artifact">2. Treat the Lockfile as a Security Artifact</h3>
<p><code>pnpm-lock.yaml</code>, <code>package-lock.json</code>, and <code>yarn.lock</code> are not just reproducibility files. They are audit surfaces.</p>
<p>After an incident, do not only inspect <code>package.json</code>. Check which versions were resolved, which tarballs were fetched, and whether any GitHub dependencies appeared unexpectedly.</p>
<h3 id="3-be-more-explicit-about-install-scripts">3. Be More Explicit About Install Scripts</h3>
<p>Disabling all scripts is not realistic for every project. But trusting every install script by default is becoming harder to defend.</p>
<p>For some CI phases, this is worth considering:</p>
<pre><code class="hljs language-bash">pnpm install --ignore-scripts
</code></pre>
<p>When a project genuinely needs build scripts, the allowed packages should be explicit. pnpm's newer build-approval direction is a healthy sign here: installation should not be an unlimited execution surface by default.</p>
<h3 id="4-reduce-horizontal-credential-exposure-in-ci">4. Reduce Horizontal Credential Exposure in CI</h3>
<p>Install jobs, test jobs, and publish jobs should not all have access to the same cloud credentials or publishing identities.</p>
<p>A simple rule works surprisingly well: if a secret is not needed during install or test, do not make it available there. If a permission is only needed during publish, open it only during publish.</p>
<h2 id="what-maintainers-should-audit">What Maintainers Should Audit</h2>
<p>If you maintain npm packages, especially packages with automated publishing, the TanStack incident is a good reason to inspect a few things.</p>
<h3 id="1-search-for-pull_request_target">1. Search for <code>pull_request_target</code></h3>
<p>The question is not "should we ban it forever?" The question is "what exactly does it do?"</p>
<p>Safer uses usually look like:</p>
<ul>
<li>read PR metadata;</li>
<li>apply labels;</li>
<li>leave comments;</li>
<li>do not checkout fork code;</li>
<li>do not execute fork code;</li>
<li>do not write cache entries later reused by release workflows.</li>
</ul>
<p>If you need to run fork-controlled code, prefer <code>pull_request</code> and accept the constraints: first-time contributor approval, lower permissions, and isolated cache behavior.</p>
<h3 id="2-do-not-let-untrusted-prs-write-production-cache">2. Do Not Let Untrusted PRs Write Production Cache</h3>
<p>A cache key is not just a string. It decides which jobs share state.</p>
<p>For fork PRs, consider:</p>
<ul>
<li>disabling cache saves;</li>
<li>scoping cache keys to PR-only contexts;</li>
<li>keeping release workflow caches separate;</li>
<li>purging caches during incident response;</li>
<li>documenting cache cleanup as part of the security playbook.</li>
</ul>
<p>The boring operational detail matters because cache survives longer than the workflow run that created it.</p>
<h3 id="3-introduce-publishing-identity-at-the-last-moment">3. Introduce Publishing Identity at the Last Moment</h3>
<p><code>id-token: write</code> should not be ambient across the entire workflow.</p>
<p>A sturdier shape is a separate publish job that only runs after tests, builds, and package-content validation pass. That job should not restore untrusted cache content, and it should not re-run install scripts that do not need to run.</p>
<p>This does not make publishing invincible. It does reduce the amount of workflow surface that can reach a publish-capable identity.</p>
<h3 id="4-pin-third-party-actions">4. Pin Third-Party Actions</h3>
<p>Floating refs are convenient: <code>@main</code>, <code>@v5</code>, <code>@latest</code>.</p>
<p>On release paths, that convenience is a standing supply-chain dependency on someone else's moving target. Pin critical third-party actions to commit SHAs. Upgrade them deliberately.</p>
<p>Slower is fine. Unbounded is not.</p>
<h3 id="5-monitor-your-own-publishes">5. Monitor Your Own Publishes</h3>
<p>TanStack was lucky in one important way: external researchers noticed quickly, and the public IOC pattern emerged fast.</p>
<p>But third-party discovery should not be the only alert.</p>
<p>Maintainers can monitor for:</p>
<ul>
<li>unexpected package versions;</li>
<li>new <code>optionalDependencies</code>;</li>
<li>unusually large files in tarballs;</li>
<li>unexpected GitHub dependencies;</li>
<li>npm provenance from the wrong workflow;</li>
<li>unusual install errors or download spikes after a release.</li>
</ul>
<p>Even a small post-publish validation script is better than finding out from a stranger's issue.</p>
<h2 id="the-part-that-stuck-with-me">The Part That Stuck With Me</h2>
<p>The phrase that kept coming back while reading the postmortem was this:</p>
<p>We confuse successful automation with safe automation.</p>
<p>CI is green. The release is automated. Provenance exists. Trusted publishing is configured. Cache restored successfully. Everything looks modern.</p>
<p>But attackers do not care whether our setup looks like best practice.</p>
<p>They care about:</p>
<ul>
<li>which code gets executed;</li>
<li>which cache gets reused;</li>
<li>which job has elevated permissions;</li>
<li>when a token appears at runtime;</li>
<li>which maintainer is too busy to manually inspect a release;</li>
<li>which install script will run by default.</li>
</ul>
<p>That is what makes supply-chain security uncomfortable. It is not usually one dramatic bug. It is the composition of reasonable systems.</p>
<h2 id="closing-thought">Closing Thought</h2>
<p>I do not think the answer is "stop using npm."</p>
<p>I do not think the answer is "stop using OIDC."</p>
<p>And I definitely do not think every open-source project can suddenly operate like a large security team. That would be unrealistic and unfair.</p>
<p>But we do need a better mental model.</p>
<p>npm package poisoning is no longer only a package-manager problem. It is a CI/CD problem, an identity problem, a cache problem, a release-governance problem, and a maintainer-load problem.</p>
<p>When I look at an automated publish workflow now, I want answers to three questions:</p>
<ol>
<li>Can untrusted code influence the trusted release environment?</li>
<li>Does cache cross the fork, main, and release trust boundaries?</li>
<li>Does the publishing identity appear only at the final publish step?</li>
</ol>
<p>If those answers are fuzzy, the risk is not theoretical.</p>
<p>Open source runs on trust. But in 2026, trust cannot only mean "everyone involved is well intentioned." It also has to mean boundaries, evidence, auditability, and a little more suspicion than feels comfortable.</p>
<p>That may be one of the core frontend engineering lessons from this new wave of npm package poisoning.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/TanStack/router/issues/7383">TanStack/router tracking issue #7383</a></li>
<li><a href="https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx">GitHub Security Advisory GHSA-g7cv-rxg3-hmpx</a></li>
<li><a href="https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/">GitHub Security Lab: Preventing pwn requests</a></li>
<li><a href="https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/">Adnan Khan: The Monsters in Your Build Cache</a></li>
<li><a href="https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised">StepSecurity: tj-actions/changed-files action is compromised</a></li>
</ul>]]></content:encoded>
      <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
      <category>npm</category>
      <category>supply-chain-security</category>
      <category>github-actions</category>
      <category>tanstack</category>
      <category>oidc</category>
      <category>ci-cd</category>
    </item>
    <item>
      <title><![CDATA[Moving a Next.js Blog to Cloudflare Workers: The Upgrade Was Easy, the Runtime Boundary Was Not]]></title>
      <link>https://hopes7.com/en/blog/next16-workers-strict-content-pipeline</link>
      <guid>https://hopes7.com/en/blog/next16-workers-strict-content-pipeline</guid>
      <description><![CDATA[The dependency upgrade was the quick part. The real work was deciding what belongs at build time and what must never run inside a Cloudflare Worker request.]]></description>
      <content:encoded><![CDATA[<h2 id="the-short-version">The Short Version</h2>
<p>This migration looked like a stack upgrade at first: Node.js 22.22.2, pnpm 11, Next.js 16, React 19, strict TypeScript, Biome, Zod, OpenNext, Wrangler, and a Cloudflare Workers deployment.</p>
<p>The version bump was not the hard part.</p>
<p>The real lesson was about runtime boundaries:</p>
<ol>
<li>A Cloudflare Worker request should not expect the original <code>content/posts</code> directory to exist.</li>
<li>A Cloudflare Worker request cannot use MDX tooling that calls <code>new Function</code> under the hood.</li>
</ol>
<p>Both problems pushed the project toward the same shape: discover, validate, and compile content at build time. At runtime, the Worker should read prepared data from the bundle and render it.</p>
<p>That sounds obvious after the fact. It did not feel obvious while chasing 500s.</p>
<h2 id="the-upgrade-itself-was-pretty-calm">The Upgrade Itself Was Pretty Calm</h2>
<p>The project now states the runtime and package manager directly:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"engines"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"node"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">">=22.22.2"</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"packageManager"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"pnpm@11.0.9"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>The main stack moved to:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"next"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"16.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"react"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"19.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"react-dom"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"19.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"typescript"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"^6.0.3"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"@biomejs/biome"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.4.14"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"zod"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"^4.4.3"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>TypeScript stayed strict, and Biome became the main lint entry:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"scripts"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"lint"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"biome check ."</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"typecheck"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"tsc --noEmit"</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>None of this is flashy. But it matters. Once deployment issues start showing up, you want fewer moving parts: one Node version, one package manager, one lockfile, one lint command, and strict type checks that keep the boring stuff honest.</p>
<h2 id="first-failure-wrangler-tried-to-configure-the-project-during-deploy">First Failure: Wrangler Tried to Configure the Project During Deploy</h2>
<p>The first Cloudflare build was a little deceptive. <code>next build</code> finished successfully, and then <code>npx wrangler deploy</code> detected a Next.js project and tried to set things up interactively.</p>
<p>That is fine on a laptop. It is not fine in a non-interactive build environment.</p>
<p>pnpm 11 also made the failure more visible:</p>
<pre><code class="hljs language-text">ERR_PNPM_IGNORED_BUILDS Ignored build scripts: workerd
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
</code></pre>
<p>The fix was not to make the deploy step smarter. The fix was to make it less responsible.</p>
<p>The project now has an explicit <code>wrangler.jsonc</code>:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"hopes"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"main"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">".open-next/worker.js"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"compatibility_date"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2026-05-09"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"compatibility_flags"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"nodejs_compat"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"assets"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"binding"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"ASSETS"</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"directory"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">".open-next/assets"</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"observability"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"logs"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"enabled"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"invocation_logs"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"traces"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"enabled"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>And a small OpenNext config:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { defineCloudflareConfig, <span class="hljs-keyword">type</span> <span class="hljs-title class_">OpenNextConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opennextjs/cloudflare'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  ...<span class="hljs-title function_">defineCloudflareConfig</span>(),
  <span class="hljs-attr">buildCommand</span>: <span class="hljs-string">'pnpm next:build'</span>,
} <span class="hljs-keyword">satisfies</span> <span class="hljs-title class_">OpenNextConfig</span>
</code></pre>
<p>The deployment pipeline became more explicit: the project builds itself, OpenNext produces the Worker output, and Wrangler deploys that output.</p>
<h2 id="second-failure-the-worker-could-not-read-contentposts">Second Failure: The Worker Could Not Read content/posts</h2>
<p>After the deploy config was fixed, the site still failed at runtime:</p>
<pre><code class="hljs language-text">no such file or directory, readdir '/bundle/content/posts'
</code></pre>
<p>That error was useful because it was blunt. The app was still trying to scan the source content directory during a Worker request.</p>
<p>That works in local Node. It does not work once the app has been bundled for Workers.</p>
<p>The content discovery step moved into a build script:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> files = (<span class="hljs-keyword">await</span> fs.<span class="hljs-title function_">readdir</span>(postsDir))
  .<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">filename</span> =></span> filename.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'.md'</span>) || filename.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'.mdx'</span>))
  .<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> a.<span class="hljs-title function_">localeCompare</span>(b))
</code></pre>
<p>The script parses each MDX file and writes a generated index:</p>
<pre><code class="hljs language-text">lib/generated/content-index.json
</code></pre>
<p>The runtime then imports that generated file:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> generatedContentPosts <span class="hljs-keyword">from</span> <span class="hljs-string">'./generated/content-index.json'</span>
</code></pre>
<p>This changed the mental model of the content system. The source directory is a build input, not a runtime dependency.</p>
<h2 id="third-failure-mdx-compilation-used-new-function">Third Failure: MDX Compilation Used new Function</h2>
<p>Fixing the content directory issue made the blog list work, but article pages were still returning 500.</p>
<p>Cloudflare’s logs had the important line:</p>
<pre><code class="hljs language-text">Code generation from strings disallowed for this context
at new Function (&#x3C;anonymous>)
at compileMDX
</code></pre>
<p>This one was easier to miss because local development worked. The normal Next.js build also worked. The failure only appeared inside the Worker runtime.</p>
<p>The article page was still calling:</p>
<pre><code class="hljs language-ts"><span class="hljs-title function_">compileMDX</span>(...)
</code></pre>
<p><code>compileMDX</code> is convenient, but in this deployment target it was the wrong place to do the work. Cloudflare Workers does not allow the kind of dynamic code generation used by that path.</p>
<p>So the content generation step grew one more responsibility. It now turns Markdown/MDX content into HTML during the build:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> markdownProcessor = <span class="hljs-title function_">remark</span>()
  .<span class="hljs-title function_">use</span>(remarkGfm)
  .<span class="hljs-title function_">use</span>(remarkToc, { <span class="hljs-attr">heading</span>: <span class="hljs-string">'h2'</span> })
  .<span class="hljs-title function_">use</span>(remarkRehype)
  .<span class="hljs-title function_">use</span>(rehypeSlug)
  .<span class="hljs-title function_">use</span>(rehypeHighlight)
  .<span class="hljs-title function_">use</span>(rehypeStringify)
</code></pre>
<p>Article pages render <code>contentHtml</code> from the generated index instead of compiling MDX during the request.</p>
<p>I also removed <code>next-mdx-remote</code> after that. Keeping an unused runtime compiler around would make it too easy to reintroduce the same issue later.</p>
<h2 id="frontmatter-also-needed-a-small-contract">Frontmatter Also Needed a Small Contract</h2>
<p>Several MDX posts were missing <code>publishedAt</code>, so the content schema and <code>BLOG.md</code> needed to agree on what a valid post looks like.</p>
<p>The important rules are simple:</p>
<ul>
<li>Always include <code>publishedAt</code>.</li>
<li>Prefer a string date like <code>"2026-05-09"</code>.</li>
<li>Use <code>"zh"</code> or <code>"en"</code> for <code>locale</code>.</li>
<li>Keep <code>categories</code> and <code>tags</code> as arrays.</li>
<li>Prefer filenames like <code>&#x3C;slug>.&#x3C;locale>.mdx</code>.</li>
</ul>
<p>This is not just documentation polish. A missing metadata field can quietly turn into a missing post in the list, which is exactly the kind of small content bug that wastes too much time later.</p>
<h2 id="what-i-verified">What I Verified</h2>
<p>The final checks were:</p>
<pre><code class="hljs language-bash">pnpm lint
pnpm typecheck
pnpm build
npx wrangler deploy --dry-run
</code></pre>
<p>I also tested the OpenNext Worker preview directly against:</p>
<pre><code class="hljs language-text">/zh/blog
/zh/blog/multica-shared-human-ai-board
/en/blog/multica-shared-human-ai-board
</code></pre>
<p>The important part was not just getting a 200. It was making sure the Worker no longer hit:</p>
<pre><code class="hljs language-text">Code generation from strings disallowed for this context
</code></pre>
<p>That is the difference between “works in Next dev” and “works where it is actually deployed.”</p>
<h2 id="what-i-would-remember-next-time">What I Would Remember Next Time</h2>
<p>Cloudflare Workers is not a regular Node server. That is easy to say, but migrations make you feel it.</p>
<p>For a content-heavy blog, I would now default to this shape:</p>
<ul>
<li>Scan posts at build time.</li>
<li>Validate frontmatter at build time.</li>
<li>Compile Markdown/MDX at build time.</li>
<li>Keep runtime rendering as small and predictable as possible.</li>
</ul>
<p>There is one trade-off: you lose some runtime flexibility. For this kind of site, that is fine. A blog does not need to compile every article on every request. It needs to be reliable, cacheable, and boring in the best possible way.</p>
<p>One last detail surprised me a little: after moving from MDX React components to generated HTML, the typography needed attention. The old component-level Tailwind classes did not magically follow the new renderer. The <code>.prose</code> styles had to carry that responsibility.</p>
<p>That is a good reminder. Shipping the page is not the same as making it feel right. For a blog, the reading experience is part of the system too.</p>]]></content:encoded>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
      <category>nextjs</category>
      <category>react</category>
      <category>typescript</category>
      <category>cloudflare-workers</category>
      <category>opennext</category>
      <category>mdx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title><![CDATA[Multica and the Shared Board for Humans and Agents]]></title>
      <link>https://hopes7.com/en/blog/multica-shared-human-ai-board</link>
      <guid>https://hopes7.com/en/blog/multica-shared-human-ai-board</guid>
      <description><![CDATA[A field note on self-hosting Multica: why Runtime binding matters, why Skills and CLI feel like the real unlock, and why the product feels more like infrastructure than a finished experience.]]></description>
      <content:encoded><![CDATA[<h1 id="multica-and-the-shared-board-for-humans-and-agents">Multica and the Shared Board for Humans and Agents</h1>
<h2 id="why-i-took-multica-seriously">Why I took Multica seriously</h2>
<p>I tried Multica recently after someone nudged me to give it a real shot. Honestly, my expectations were low.</p>
<p>I assumed it would be one more AI workflow wrapper. Another dashboard trying to glue chat, tasks, and automation together and calling it an Agent platform.</p>
<p>But after actually running it on my home Mac mini M4, my opinion shifted.</p>
<p>What caught my attention wasn't the model list, or the idea of dispatching multiple agents in parallel. It was something much more basic: <strong>Multica puts humans and agents on the same board.</strong></p>
<p>That sounds almost boring. But in practice, it's exactly the missing layer in a lot of current AI tooling.</p>
<p>How does a human hand off work? How does an agent pick it up? Where do intermediate updates go? Who makes the final call? What gets recorded when things go wrong? If those answers live across half a dozen terminals, chats, and ad hoc scripts, collaboration still feels improvised.</p>
<p>Multica doesn't solve every problem here, but it does pick the right interface to build around.</p>
<h2 id="what-it-actually-is">What it actually is</h2>
<p>If I had to summarize it in one line, Multica is a collaboration platform where humans and AI agents share the same task board.</p>
<p>The flow is refreshingly clear:</p>
<ol>
<li>A human creates an issue.</li>
<li>The issue gets assigned to an agent.</li>
<li>The agent picks it up on its bound Runtime, clones the repo, and does the work.</li>
<li>Progress and results are written back into the same issue.</li>
<li>The issue moves to <code>In Review</code>, not straight to <code>Done</code>.</li>
</ol>
<p>That <code>In Review</code> detail matters more than it seems. It encodes a healthy default: <strong>agents execute, humans approve.</strong></p>
<p>And if you're terminal-native, the CLI path is already pretty usable:</p>
<pre><code class="hljs language-bash">multica issue create --title <span class="hljs-string">"Write a post about Multica"</span>
multica agent list --output json
multica daemon status
multica issue comment add &#x3C;issue-id> --content <span class="hljs-string">"Draft is ready"</span>
</code></pre>
<p>That's why I don't see it as just an "AI kanban." It feels closer to an interface layer for agent collaboration.</p>
<h2 id="deployment-was-mostly-smooth-with-two-catches">Deployment was mostly smooth, with two catches</h2>
<p>From an infrastructure perspective, Multica is fairly approachable: Go backend, Next.js frontend, PostgreSQL, and a straightforward <code>docker compose</code> story.</p>
<p>I deployed it with OrbStack and Cloudflare Tunnel, using separate subdomains for the frontend and backend. Overall, it was a lot less painful than I expected.</p>
<p>Still, there were two catches worth calling out.</p>
<p>The first one is the default compose setup. It leans toward prebuilt images, which is fine until you want something more opinionated like a dual-domain setup with a custom WebSocket address. At that point, it becomes easier to switch to source builds than to keep stretching the default configuration.</p>
<p>The second one is <code>APP_ENV=production</code>. Once that is enabled, the universal verification code <code>888888</code> is disabled. If you haven't already configured Resend email, it's surprisingly easy to lock yourself out during first-time setup.</p>
<p>Neither issue is dramatic, but both are the kind of thing you'll want to know in advance.</p>
<p>Even so, my overall take on deployment is positive. Once the system is up, the operational path is clear, and the CLI is much better than I expected for day-to-day work.</p>
<h2 id="runtime-is-the-feature-that-made-me-stop-and-think">Runtime is the feature that made me stop and think</h2>
<p>The mental model is clean:</p>
<p><code>Workspace -> Project -> Repo -> Issue -> Agent -> Runtime</code></p>
<p>The interesting part is the last two layers.</p>
<p>An agent is bound to a Runtime, and a Runtime is bound to a machine. That means you're not vaguely saying "let an agent do this." You're saying "let this agent do this on that machine."</p>
<p>That turns out to be a very practical abstraction.</p>
<p>Real developer environments are not interchangeable. Some tasks only work on a specific machine because of internal network access, certificates, local toolchains, or cached state. In a lot of teams, that reality breaks automation and pushes work back into manual handling.</p>
<p>Runtime binding turns it into a scheduling problem instead.</p>
<p>If the company laptop is running a daemon, and an agent is bound to that Runtime, the task naturally lands there. That doesn't look flashy in a product demo, but it's exactly the kind of constraint that determines whether agent workflows survive contact with real work.</p>
<h2 id="skills-and-the-cli-are-the-second-layer-of-value">Skills and the CLI are the second layer of value</h2>
<p>If Runtime answers "where does the work run," Skills answer "how does the work become reusable."</p>
<p>This is where Multica starts to feel different from plain task-routing tools.</p>
<p>When an agent solves something once, the solution can be captured as a Skill and reused across the workspace. Deployment playbooks, review checklists, migration routines, debugging sequences. Knowledge stops living only in one person's head and starts becoming an executable team asset.</p>
<p>That's a bigger deal than it sounds.</p>
<p>It's not just a prompt library. It's not just internal documentation. It's much closer to a reusable operational primitive.</p>
<p>Then the CLI reinforces the same idea. An agent doesn't need a browser. It needs a way to:</p>
<ul>
<li>create or pick up tasks</li>
<li>leave updates</li>
<li>change status</li>
<li>trigger other agents</li>
</ul>
<p>Those actions fit a CLI far better than a dashboard.</p>
<p>That's when another thought clicked for me: <strong>Multica may be designed for humans on the surface, but its best user might actually be the agent itself.</strong></p>
<p>Humans are still in the loop, obviously. But the system feels strongest when agents can coordinate around the interface rather than waiting for humans to orchestrate every step manually.</p>
<h2 id="it-also-exposes-a-deeper-problem">It also exposes a deeper problem</h2>
<p>What makes Multica interesting isn't only what it gets right today. It's also the question it quietly surfaces about what comes next.</p>
<p>Right now, the default flow still looks like this:</p>
<pre><code class="hljs language-text">Human defines the task boundary -> Human creates the issue -> Agent executes -> Human reviews
</code></pre>
<p>That makes sense today. Models are still not reliable or autonomous enough to own full task decomposition most of the time.</p>
<p>But if agent capability keeps improving, the more natural flow may start looking like this:</p>
<pre><code class="hljs language-text">Human states a goal -> Agents define tasks -> Agents split and coordinate work -> Human reviews key checkpoints
</code></pre>
<p>At that point, issue-driven collaboration may start to feel a bit heavy. Not because it's wrong, but because it belongs to a stage where humans still draw every boundary by hand.</p>
<p>That's why I see Multica as a transitional product in the best sense. It's grounded in today's constraints, but it already points toward the next interface problem.</p>
<p>Either it grows into something closer to an operating system for agents, or eventually it gets replaced by more native agent-to-agent coordination protocols.</p>
<h2 id="why-i-currently-think-of-it-as-infrastructure">Why I currently think of it as infrastructure</h2>
<p>There's also a very practical lens here: utilization.</p>
<p>A lot of people now pay for multiple AI coding tools at the same time. The question is no longer "do I have access to a capable model?" It's "am I actually using the compute I've already paid for well?"</p>
<p>From that angle, tools like Multica have a simple job: keep agents doing useful work even when you're not actively sitting in front of the terminal.</p>
<p>That is why I think of it less as an instant productivity miracle and more as an infrastructure investment.</p>
<p>It hasn't yet delivered the kind of gain that makes me want to move everything into it overnight. But it feels like the kind of system you want connected early, with daemons running, machines registered, and workflows slowly accumulating around it.</p>
<p>If agent capability keeps improving, the return on that infrastructure should compound.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>My current take on Multica is pretty simple: <strong>it's already useful today, but its biggest value probably hasn't arrived yet.</strong></p>
<p>What I find compelling is not any single feature. It's the fact that Multica is trying to answer a serious interface question: what should collaboration look like when humans and agents are both real participants in the same system?</p>
<p>If that question gets answered well, a lot of the agent workflows that still feel experimental right now will suddenly feel normal.</p>
<p>So my conclusion isn't "move everything into Multica tomorrow."</p>
<p>It's more conservative than that, and probably more durable:</p>
<p>Install it. Keep the daemon running. Let machines and agents start building a working relationship.</p>
<p>The rest of the value may show up as the models catch up.</p>]]></content:encoded>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <category>multica</category>
      <category>agents</category>
      <category>self-hosting</category>
      <category>automation</category>
      <category>workflow</category>
    </item>
    <item>
      <title><![CDATA[Claude Getting Dumber, GPT Getting Throttled: What's Happening with AI Providers?]]></title>
      <link>https://hopes7.com/en/blog/ai-model-quality-drop-april-2026</link>
      <guid>https://hopes7.com/en/blog/ai-model-quality-drop-april-2026</guid>
      <description><![CDATA[In April 2026, Claude showed significant quality degradation and a major outage, while OpenAI adjusted ChatGPT Pro pricing and rate limiting. What's behind it all?]]></description>
      <content:encoded><![CDATA[<h1 id="claude-getting-dumber-gpt-getting-throttled-whats-happening-with-ai-providers">Claude Getting Dumber, GPT Getting Throttled: What's Happening with AI Providers?</h1>
<h2 id="youve-probably-noticed-too">You've probably noticed too</h2>
<p>If you've been coding with AI lately, you might have noticed Claude feeling less sharp, or GPT running out of quota faster than before. It's not your imagination.</p>
<p>In April 2026, both issues blew up simultaneously — and neither is small.</p>
<h2 id="claude-from-developer-darling-to-public-enemy">Claude: From Developer Darling to Public Enemy</h2>
<h3 id="quality-cliff-dive">Quality cliff dive</h3>
<p>Between April 9th and 10th, numerous Claude Code users reported a sudden degradation in reasoning quality:</p>
<ul>
<li><strong>Simple bug fixes</strong>: What took 1-2 attempts now requires 7-10 rounds of trial and error</li>
<li><strong>Stopped using specialized tools</strong>: The model started ignoring <code>/plan</code>, <code>/bug</code> commands, and no longer routed correctly to subagents</li>
<li><strong>Shallower reasoning</strong>: Responses became more surface-level, lacking deep analysis</li>
</ul>
<p>A GitHub issue (<a href="https://github.com/anthropics/claude-code/issues/46143">#46143</a>) documented this change in detail — no client update, no config change, just suddenly dumber.</p>
<h3 id="even-claude-admits-it">Even Claude admits it</h3>
<p>The Register ran an interesting experiment: they asked Claude to analyze quality complaints in its own GitHub repo. The results:</p>
<ul>
<li><strong>Jan-Feb</strong>: ~5 quality complaints per month on average</li>
<li><strong>March</strong>: Surged to 18 (a 3.5× jump)</li>
<li><strong>First 13 days of April</strong>: Already 20+, on track to exceed March</li>
</ul>
<h3 id="the-bigger-event-amd-executives-data-report">The bigger event: AMD executive's data report</h3>
<p>On April 2nd, Stella Laurenzo, Senior Director of AMD's AI Group, filed what might be the most data-rich bug report in AI history on Anthropic's GitHub:</p>
<ul>
<li>Analyzed <strong>6,852 session files</strong></li>
<li>Counted <strong>234,760 tool calls</strong></li>
<li>Found reads-per-edit dropped from <strong>6.6x to 2.0x</strong></li>
</ul>
<p>That's a drop of nearly 70%. A product's core metric losing 70% in a week would be unacceptable in any industry.</p>
<p>Translation: Claude stopped "reading code" before modifying it — edit without reading, introduce bugs, try again, repeat.</p>
<h3 id="anthropics-confirmed-changes">Anthropic's confirmed changes</h3>
<ul>
<li><strong>March 3</strong>: Default effort level changed from high to medium</li>
<li><strong>Feb 9</strong>: Adaptive thinking shipped, but thinking token UI display may have been hidden</li>
<li><strong>March 26</strong>: Peak-hour throttling introduced, reducing available tokens within 5-hour sessions</li>
<li><strong>Prompt cache TTL bug</strong>: Causing cost inflation of 10-20×</li>
</ul>
<p>In short: <strong>Anthropic lowered the model's "effort level" to manage costs and cope with compute constraints. What users perceive is "it got dumber."</strong></p>
<h3 id="april-13th-major-outage">April 13th major outage</h3>
<p>Adding insult to injury, Claude suffered a major outage on April 13th, affecting both Claude.ai and Claude Code for approximately 48 minutes, further fueling user frustration.</p>
<h2 id="openai-price-cuts--rate-limiting">OpenAI: Price cuts + rate limiting</h2>
<h3 id="chatgpt-pro-price-halved">ChatGPT Pro price halved</h3>
<p>On April 10th, OpenAI announced ChatGPT Pro dropping from $200/month to $100/month. Sounds great on the surface, but the underlying logic is worth examining:</p>
<ul>
<li>Price cuts usually mean user growth is below expectations, or the cost structure has shifted</li>
<li>Simultaneously launched GPT-5.4 mini as a fallback model — automatically downgrading when rate limits are hit</li>
</ul>
<h3 id="invisible-quota-reduction">Invisible quota reduction</h3>
<p>While the price dropped, many users report tighter limits:</p>
<ul>
<li>GPT-5.4 Thinking usage restrictions are stricter</li>
<li>More frequent fallbacks to GPT-5.4 mini</li>
<li>Manual Thinking mode has a hard cap of 3,000 requests/week</li>
</ul>
<p><strong>The cost of lower prices is more aggressive throttling and model downgrades.</strong></p>
<h2 id="whats-really-going-on">What's really going on?</h2>
<h3 id="compute-bottleneck-is-the-core-issue">Compute bottleneck is the core issue</h3>
<p>Both companies' behavior points to the same conclusion: <strong>AI compute supply and demand are severely imbalanced.</strong></p>
<ol>
<li><strong>Explosive demand growth</strong>: AI coding tools (Claude Code, Codex, Cursor, etc.) saw explosive user growth in Q1 2026, with enterprise users flooding in</li>
<li><strong>Supply can't keep up</strong>: GPU capacity expansion takes time — new datacenter build cycles are typically 12-18 months</li>
<li><strong>Massive cost pressure</strong>: Inference costs for large models remain high, and price wars are amplifying losses</li>
</ol>
<h3 id="provider-strategies-compared">Provider strategies compared</h3>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>Anthropic</th>
<th>OpenAI</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lower model "effort"</td>
<td>✅ Default effort → medium</td>
<td>❌</td>
</tr>
<tr>
<td>Peak-hour throttling</td>
<td>✅ Elastic 5-hour session definition</td>
<td>✅ More frequent fallbacks</td>
</tr>
<tr>
<td>Small model fallback</td>
<td>❌</td>
<td>✅ GPT-5.4 mini</td>
</tr>
<tr>
<td>Direct price cuts</td>
<td>❌</td>
<td>✅ Pro -50%</td>
</tr>
<tr>
<td>Outages</td>
<td>✅ April 13 major outage</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>It's all the same playbook: <strong>when compute is insufficient, use every lever to reduce per-user resource consumption.</strong></p>
<h3 id="what-this-means-for-users">What this means for users</h3>
<ol>
<li><strong>AI coding tool costs are no longer "negligible."</strong> My 150M tokens last week (with RTK) was already compressed. Without optimization tools, costs would be much higher</li>
<li><strong>Model quality may continue to fluctuate.</strong> Providers prioritize enterprise customers when compute is scarce — individual user experience may suffer</li>
<li><strong>Tooling matters more than ever.</strong> Token compression tools like RTK have gone from "nice to have" to "must have." Multi-model strategies make more sense too</li>
<li><strong>Small models have their moment.</strong> GPT-5.4 mini shows providers are serious about "good enough" models that may be more cost-effective for specific use cases</li>
</ol>
<h2 id="my-playbook">My playbook</h2>
<p>As a heavy AI coding user, here's my current strategy:</p>
<ol>
<li><strong>RTK always on</strong>: Validated 57% token savings with zero learning curve</li>
<li><strong>Multi-model mixing</strong>: Don't rely on a single model — if Claude struggles, switch to GPT, and vice versa</li>
<li><strong>Off-peak usage</strong>: Anthropic themselves said off-peak hours offer better experience</li>
<li><strong>Local-first for simple tasks</strong>: Reserve big model quotas for complex work</li>
</ol>
<h2 id="the-real-problem-opacity">The real problem: opacity</h2>
<p>A compute shortage is understandable. What actually frustrated users was how it was handled.</p>
<p>Anthropic never candidly said "we're compute-constrained and need temporary throttling" — instead the default effort was quietly lowered, thinking token display was hidden, and throttling was packaged as "elastic definitions." OpenAI announced a price cut while silently switching users to the mini fallback model more often, without clear notice. You pay the same money and may receive a downgraded service, with no way to notice.</p>
<p>Users aren't stupid. They know exactly what they're getting and what they aren't. Instead of investing in spin, invest in capacity — give users the best product, not the cheapest excuse.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>The AI industry is in an interesting phase: technical capabilities are advancing rapidly, but infrastructure hasn't caught up. Providers are struggling to balance user growth, cost control, and experience quality.</p>
<p>For developers, the best response isn't complaining about dumber models — it's building a workflow that doesn't depend on a single provider. After all, tools exist to serve us, not the other way around.</p>
<hr>
<p><em>Related posts:</em></p>
<ul>
<li><em>Previous: <a href="/posts/rtk-token-consumption-week-test">150M Tokens in One Week: My Codex Usage with RTK</a></em></li>
<li><em>Earlier: <a href="/posts/codex-token-usage-weekly-record">350M Tokens in 11 Days: My Codex Usage Record</a></em></li>
</ul>]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <category>claude</category>
      <category>openai</category>
      <category>gpt</category>
      <category>anthropic</category>
      <category>ai</category>
      <category>model quality</category>
      <category>industry</category>
    </item>
    <item>
      <title><![CDATA[150M Tokens in One Week: My Codex Usage with RTK]]></title>
      <link>https://hopes7.com/en/blog/rtk-token-consumption-week-test</link>
      <guid>https://hopes7.com/en/blog/rtk-token-consumption-week-test</guid>
      <description><![CDATA[After consuming 350M tokens last week, adding RTK brought it down to 150M — a 57% reduction. Real usage data and experience.]]></description>
      <content:encoded><![CDATA[<h1 id="150m-tokens-in-one-week-my-codex-usage-with-rtk">150M Tokens in One Week: My Codex Usage with RTK</h1>
<h2 id="background">Background</h2>
<p><a href="/posts/codex-token-usage-weekly-record">Last week</a> I logged 350M tokens consumed over 11 days and mentioned I'd try RTK to optimize. One week later, the results are in: <strong>150M tokens</strong>.</p>
<p>Same workflow — Codex 5.4 and 5.3-codex mixed, the same write-review-revise loop, similar project complexity. The only change: RTK.</p>
<h2 id="data-comparison">Data Comparison</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Last Week (No RTK)</th>
<th>This Week (With RTK)</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr>
<td>Token consumption</td>
<td>350M</td>
<td>150M</td>
<td><strong>-57%</strong></td>
</tr>
<tr>
<td>Models used</td>
<td>5.4 + 5.3-codex</td>
<td>5.4 + 5.3-codex</td>
<td>Same</td>
</tr>
<tr>
<td>Workflow</td>
<td>Write-review-revise loop</td>
<td>Same</td>
<td>Same</td>
</tr>
<tr>
<td>Dev tasks</td>
<td>Feature work, doc iteration, code review</td>
<td>Same</td>
<td>Same</td>
</tr>
</tbody>
</table>
<p>From 350M down to 150M — 200M tokens saved. RTK's compression performed better than I expected.</p>
<h2 id="rtk-experience">RTK Experience</h2>
<h3 id="biggest-takeaway-invisible-but-effective">Biggest takeaway: invisible, but effective</h3>
<p>Honestly, after a week with RTK, I barely notice it's there. It's a transparent proxy — AI tools do exactly what they did before, and output quality feels unchanged.</p>
<p>But when you check the usage stats, the difference speaks for itself.</p>
<h3 id="where-compression-shines">Where compression shines</h3>
<ul>
<li><strong><code>git diff</code> / <code>git status</code></strong>: Large project diffs are extremely verbose. RTK compresses them into a concise version that AI can understand just as well</li>
<li><strong>Test output</strong>: Massive repetitive pass/fail logs get folded, keeping only key information</li>
<li><strong>Error logs</strong>: Stack traces get truncated to essential frames instead of stuffing the entire call chain into context</li>
<li><strong>File listings</strong>: <code>find</code>, <code>ls</code> outputs get grouped by directory with deduplication</li>
</ul>
<h3 id="where-it-doesnt-make-much-difference">Where it doesn't make much difference</h3>
<ul>
<li>Direct coding conversations — token usage is already low, not much to compress</li>
<li>Short command outputs — minimal difference before and after compression</li>
</ul>
<h2 id="what-the-saved-tokens-mean">What the saved tokens mean</h2>
<ol>
<li><strong>Cost cut in half</strong>. At last week's burn rate, a month would be over a billion tokens. The savings are real money</li>
<li><strong>Context window lasts longer</strong>. The same context window fits more conversation turns, so long tasks don't get truncated as easily</li>
<li><strong>Less anxiety about context limits</strong>. No more worrying "this diff is too long, will it blow up the context"</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Last week I said "next week's post will have the test results — let's see if RTK is a cost-saver or just hype." The verdict is clear: <strong>it's a cost-saver</strong>.</p>
<p>57% token reduction, zero learning curve (<code>rtk init</code> and you're done), virtually no impact on the development experience. For anyone heavily using Codex or similar AI coding tools, RTK is essentially a must-install.</p>
<hr>
<p><em>Previous: <a href="/posts/codex-token-usage-weekly-record">350M Tokens in 11 Days: My Codex Usage Record</a></em></p>]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <category>codex</category>
      <category>rtk</category>
      <category>ai</category>
      <category>token</category>
      <category>development efficiency</category>
      <category>ai-coding</category>
    </item>
    <item>
      <title><![CDATA[350 Million Tokens in 11 Days: My Codex Usage Record]]></title>
      <link>https://hopes7.com/en/blog/codex-token-usage-weekly-record</link>
      <guid>https://hopes7.com/en/blog/codex-token-usage-weekly-record</guid>
      <description><![CDATA[Documenting my token consumption over 11 days of daily development with Codex 5.4 and 5.3, and introducing RTK as an optimization experiment.]]></description>
      <content:encoded><![CDATA[<h1 id="350-million-tokens-in-11-days-my-codex-usage-record">350 Million Tokens in 11 Days: My Codex Usage Record</h1>
<p>Over the past 11 days, I've been using Codex heavily for daily development. Before I knew it, my token usage had climbed to 350 million. Here's a breakdown of what happened behind that number.</p>
<h2 id="the-numbers">The Numbers</h2>
<ul>
<li><strong>Period</strong>: March 30 – April 9, 2026 (11 days)</li>
<li><strong>Total Token Consumption</strong>: ~350 million</li>
<li><strong>Models Used</strong>: Codex 5.4 (primary), Codex 5.3</li>
<li><strong>Main Use Case</strong>: Daily coding and documentation</li>
</ul>
<h2 id="how-i-used-the-models">How I Used the Models</h2>
<p>The two models served distinct purposes:</p>
<ul>
<li><strong>Codex 5.4</strong>: Used for writing documentation — higher quality output, more accurate expression</li>
<li><strong>Codex 5.3</strong>: Used for rewriting and iterating on top of 5.4's output</li>
</ul>
<p>My actual workflow: draft with Codex 5.4 first, then rewrite and refine with Codex 5.3, followed by repeated review cycles — having the AI review its own writing, suggest changes, and iterate. This "write-review-revise" loop consumed the bulk of the 350 million tokens.</p>
<h2 id="what-ate-all-those-tokens">What Ate All Those Tokens</h2>
<p>350 million sounds like a lot, and honestly, most of it came from:</p>
<ul>
<li>Implementing and iterating on complex features</li>
<li>Code review and refactoring suggestions</li>
<li>Long-context conversations (feeding entire files to the AI for context)</li>
</ul>
<p>The productivity gains from AI-assisted coding are real, but the token cost is hard to ignore. Especially with the documentation workflow — the "write-review-revise" cycle produces high-quality docs, but tokens pile up fast. I need to think about optimizing this process: reducing unnecessary review rounds, managing context length, and keeping token usage in check without sacrificing output quality.</p>
<h2 id="the-optimization-plan-enter-rtk">The Optimization Plan: Enter RTK</h2>
<p>Starting today, I'm introducing <a href="https://github.com/rtk-ai/rtk">RTK</a> to cut down on wasted tokens. RTK is a CLI proxy that filters and compresses command output before it reaches the LLM, claiming to reduce token consumption by 60-90%.</p>
<p>It handles common dev command outputs — <code>git diff</code>, <code>git log</code>, <code>cat</code>, <code>grep</code>, test results, and more — stripping out the noise and keeping only what matters.</p>
<h3 id="how-it-works">How It Works</h3>
<p>RTK transparently rewrites Bash commands via hooks (e.g., turning <code>git status</code> into <code>rtk git status</code>). The AI tool never sees the rewrite — it just receives compressed output.</p>
<p>It applies four strategies per command type:</p>
<ul>
<li><strong>Smart Filtering</strong> — Removes noise (comments, whitespace, boilerplate)</li>
<li><strong>Grouping</strong> — Aggregates similar items (files by directory, errors by type)</li>
<li><strong>Truncation</strong> — Keeps relevant context, cuts redundancy</li>
<li><strong>Deduplication</strong> — Collapses repeated log lines with counts</li>
</ul>
<p>It supports Claude Code, Codex, Copilot, Gemini CLI, Cursor, Windsurf, Cline, and more — one command to set up.</p>
<p>Over the next week, I'll run a real-world development test comparing token usage with and without RTK using the same workflow. Results will be shared in a follow-up post.</p>]]></content:encoded>
      <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
      <category>codex</category>
      <category>ai</category>
      <category>token</category>
      <category>developer-productivity</category>
      <category>ai-coding</category>
    </item>
    <item>
      <title><![CDATA[Frontend Security: Isolating Sensitive Input with Sandbox Iframes]]></title>
      <link>https://hopes7.com/en/blog/checkout-secure-address-iframe</link>
      <guid>https://hopes7.com/en/blog/checkout-secure-address-iframe</guid>
      <description><![CDATA[How we isolated address and document input from the main checkout page using sandbox iframes and a middleware layer — with minimal changes to existing code.]]></description>
      <content:encoded><![CDATA[<h2 id="the-problem-compliance-pressure-vs-legacy-code">The Problem: Compliance Pressure vs. Legacy Code</h2>
<p>E-commerce checkout pages handle sensitive data by nature — addresses, ID numbers, tax IDs. What makes it worse: the rise of AI tools has dramatically lowered the barrier for attackers. XSS injection and MITM attacks that once required real skill can now be automated — LLMs can generate exploit scripts, scan for vulnerabilities, and even mimic normal user behavior to bypass security controls. Security audits are getting stricter: <strong>sensitive input should not happen in the main page context</strong>.</p>
<p>But here's the reality — checkout address modules accumulate years of iteration, deep coupling, and fragile dependencies. A full rewrite isn't practical when you have a shipping deadline.</p>
<p>So the question became: <strong>how do we isolate sensitive input without rewriting the main checkout flow?</strong></p>
<h2 id="why-not-rewrite-everything">Why Not "Rewrite Everything"</h2>
<p>We evaluated several approaches:</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Pros</th>
<th>Cons</th>
</tr>
</thead>
<tbody>
<tr>
<td>Full page rewrite</td>
<td>Clean slate</td>
<td>Long timeline, high regression risk</td>
</tr>
<tr>
<td>Client-side encryption</td>
<td>Small changes</td>
<td>Key management is a new attack surface</td>
</tr>
<tr>
<td>Tokenization</td>
<td>Most secure</td>
<td>Requires backend model changes across all consumers</td>
</tr>
<tr>
<td><strong>Sandbox iframe + middleware</strong></td>
<td><strong>Minimal changes, effective isolation</strong></td>
<td><strong>Read-only data still visible on main page</strong></td>
</tr>
</tbody>
</table>
<p>We went with the last option. The core judgment: <strong>isolating the input process solves the biggest security pain point in phase one</strong>. Read-only display on the main page can be addressed later.</p>
<h2 id="architecture-three-layers-clear-boundaries">Architecture: Three Layers, Clear Boundaries</h2>
<h3 id="1-parent-page-checkout">1. Parent Page (Checkout)</h3>
<ul>
<li>Continues handling address list queries and display (unchanged in phase one)</li>
<li>Add/Edit buttons now open an iframe modal</li>
<li>Requests a short-lived session token for each operation</li>
<li>Listens for save-success messages from the iframe, then refreshes the address list</li>
</ul>
<p>What the parent page does <strong>not</strong> do: render sensitive forms, read iframe DOM, or observe user input keystrokes.</p>
<h3 id="2-collector-sandbox-iframe">2. Collector (Sandbox Iframe)</h3>
<ul>
<li>Renders the full address/document form independently</li>
<li>Manages its own form state and validation</li>
<li>Fetches initialization data (country lists, edit prefill) from the middleware using the session token</li>
<li>Submits through the middleware — never connects directly to backend APIs</li>
<li>Notifies the parent via <code>postMessage</code> on successful save</li>
</ul>
<p>Key design choice: a single collector supports two modes via a <code>scope</code> parameter — full address form or document-only form. One page, two use cases.</p>
<h3 id="3-middleware-node-facade">3. Middleware (Node Facade)</h3>
<p>This is the funnel point for all sensitive operations:</p>
<ul>
<li>Manages short-lived session tokens (30-minute TTL, invalidated immediately after successful submit)</li>
<li>Aggregates country lists and edit prefill data for the iframe</li>
<li>Forwards address save and document save requests to real backend services</li>
<li>Applies uniform log sanitization and parameter filtering</li>
</ul>
<p>The middleware does <strong>not</strong> store address plaintext, maintain long-term drafts, or replace the existing address list data source.</p>
<h2 id="session-management-security-meets-ux">Session Management: Security Meets UX</h2>
<p>Session token design is the security backbone:</p>
<ul>
<li><strong>Short-lived</strong>: Fixed 30-minute expiry, immediate invalidation after successful submission</li>
<li><strong>User-bound</strong>: Tokens are bound to the current user, site, and operation mode</li>
<li><strong>Single-session constraint</strong>: Only one active session per page; creating a new one automatically invalidates the old</li>
<li><strong>Edit permission check</strong>: In edit mode, the middleware verifies the user owns the address being edited</li>
</ul>
<p>Even if a token leaks, the attack window is narrow and cross-user usage is impossible.</p>
<h2 id="submit-strategy-handling-multi-api-failure-scenarios">Submit Strategy: Handling Multi-API Failure Scenarios</h2>
<p>Real-world saves involve multiple backend calls (address body, document info, default address sync). The failure handling strategy needed careful thought:</p>
<p><strong>Execution order</strong>: Save address body → Save document info → Handle default address and billing address sync</p>
<p><strong>Failure scenarios</strong>:</p>
<ul>
<li><strong>Address saved, document save failed</strong>: No compensating delete (in create mode). Return "partial save" status, prompt user to verify</li>
<li><strong>Address and document saved, default address sync failed</strong>: Same "partial save" treatment</li>
<li><strong>Partial failure in edit mode</strong>: No automatic rollback promised. Prompt user to retry or manually refresh</li>
</ul>
<p>Core principle: <strong>It's better to honestly tell the user "please verify" than to pretend everything succeeded.</strong></p>
<h2 id="message-validation-dont-let-postmessage-become-a-new-attack-surface">Message Validation: Don't Let postMessage Become a New Attack Surface</h2>
<p>Parent-iframe communication via <code>postMessage</code> is itself a potential vector. Our defenses:</p>
<ul>
<li>Verify message source is the iframe's <code>contentWindow</code></li>
<li>Generate a random nonce per session; iframe must include it in responses</li>
<li>Only handle whitelisted message types (e.g., save-success); ignore everything else</li>
<li>Don't rely on <code>event.origin</code> as the sole trust signal</li>
</ul>
<h2 id="what-we-didnt-do-being-honest">What We Didn't Do (Being Honest)</h2>
<p>Phase one has clear boundaries:</p>
<ul>
<li>❌ Main page still displays full addresses in the list</li>
<li>❌ Middleware and backend still see full addresses during save/prefill</li>
<li>❌ No frontend rewrite for delete address or set-default-address flows</li>
</ul>
<p>These aren't oversights — they're deliberate trade-offs. Phase one's goal is <strong>input process isolation</strong>, not <strong>full-pipeline data desensitization</strong>.</p>
<h2 id="rollout-starting-with-a-single-site">Rollout: Starting with a Single Site</h2>
<p>Phase one is controlled via feature flags, launching first on a single site (Brazil). Key metrics we're watching:</p>
<ul>
<li>Session expiration rate</li>
<li>Mobile collector open success rate</li>
<li>Create/edit save success rate</li>
<li>Partial save error rate</li>
</ul>
<p>Expansion to other sites happens only after stability is confirmed.</p>
<h2 id="takeaways">Takeaways</h2>
<p>Looking back, the biggest lesson: <strong>security improvements don't always require a big-bang rewrite</strong>.</p>
<p>Find the smallest meaningful cut, solve the biggest risk first, then expand incrementally. It's more practical than waiting for a "perfect" solution that never ships.</p>
<p>Of course, sandbox iframes aren't a silver bullet. Full-pipeline desensitization (including main page display) will require deeper data model changes. But that's phase two's problem.</p>
<p>Solve what's in front of you first. Idealism can wait.</p>]]></content:encoded>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <category>frontend-security</category>
      <category>iframe</category>
      <category>sandbox</category>
      <category>security-architecture</category>
      <category>e-commerce</category>
    </item>
    <item>
      <title><![CDATA[OpenClaw Gotchas: Gateway Device Pairing & Cron Job Setup]]></title>
      <link>https://hopes7.com/en/blog/openclaw-gateway-pairing-troubleshooting</link>
      <guid>https://hopes7.com/en/blog/openclaw-gateway-pairing-troubleshooting</guid>
      <description><![CDATA[How to fix the "pairing required" error when running openclaw cron commands, and the correct way to approve device pairing with openclaw devices approve.]]></description>
      <content:encoded><![CDATA[<h2 id="the-setup">The Setup</h2>
<p>I've been playing with OpenClaw lately — a framework that connects AI assistants to chat platforms like Telegram and Discord. The overall experience has been smooth, but I hit a frustrating roadblock when setting up scheduled cron jobs. Here's what happened and how I fixed it.</p>
<h2 id="the-problem-pairing-required">The Problem: pairing required</h2>
<p>I wanted my bot to send me a daily AI news digest at 9 AM every morning. So I asked the bot to create a cron job from our Telegram chat.</p>
<p>The bot ran <code>openclaw cron add</code>, and got this error:</p>
<pre><code>gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
</code></pre>
<p>The CLI couldn't talk to the Gateway because the device hadn't been paired yet.</p>
<h3 id="gotcha-1-the-wrong-pairing-command">Gotcha #1: The wrong pairing command</h3>
<p>My first instinct was to check the docs and try <code>openclaw gateway pair</code> — seems logical, right? It didn't work.</p>
<p>The correct way is through the <code>openclaw devices</code> subcommand:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Check available subcommands first</span>
openclaw devices --<span class="hljs-built_in">help</span>

<span class="hljs-comment"># Then approve the pairing</span>
openclaw devices approve
</code></pre>
<p>After running <code>openclaw devices approve</code>, the CLI could communicate with the Gateway normally.</p>
<h3 id="gotcha-2-unpaired-commands-crash-the-gateway">Gotcha #2: Unpaired commands crash the Gateway</h3>
<p>This one hurts. If you let the bot try running <code>openclaw cron add</code> before pairing is done, it doesn't just fail — <strong>the Gateway drops the connection entirely</strong>.</p>
<p>That means your bot goes offline and needs to reconnect. If you're operating through a chat interface, that's pretty frustrating.</p>
<p>The correct order:</p>
<ol>
<li>Complete device pairing first: <code>openclaw devices approve</code></li>
<li>Then let the bot create scheduled tasks</li>
</ol>
<h2 id="after-pairing-creating-the-cron-job">After Pairing: Creating the Cron Job</h2>
<p>Once pairing is sorted, creating scheduled jobs is straightforward:</p>
<pre><code class="hljs language-bash">openclaw cron add \
  --name <span class="hljs-string">"AI Daily Brief"</span> \
  --cron <span class="hljs-string">"0 9 * * *"</span> \
  --tz <span class="hljs-string">"Asia/Shanghai"</span> \
  --session isolated \
  --message <span class="hljs-string">"Search for AI news from the past 24 hours..."</span> \
  --announce \
  --channel telegram \
  --to <span class="hljs-string">"your_chat_id"</span>
</code></pre>
<p>Key parameters:</p>
<ul>
<li><code>--session isolated</code>: Runs in an isolated session, doesn't interfere with your main chat</li>
<li><code>--announce</code>: Delivers the result to the specified channel after execution</li>
<li><code>--cron</code> + <code>--tz</code>: Cron expression with timezone for scheduling</li>
<li><code>--channel telegram --to &#x3C;id></code>: Specifies the delivery target</li>
</ul>
<h2 id="model-choice">Model Choice</h2>
<p>I'm using <code>zai/glm-5-turbo</code> (Zhipu GLM-5 Turbo) as the model. So far it's been solid — fast responses, good Chinese language understanding, more than enough for daily assistant tasks.</p>
<h2 id="summary">Summary</h2>
<table>
<thead>
<tr>
<th>Problem</th>
<th>Solution</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pairing required</code> error</td>
<td>Run <code>openclaw devices approve</code></td>
</tr>
<tr>
<td><code>openclaw gateway pair</code> doesn't work</td>
<td>Device pairing uses <code>openclaw devices</code>, not <code>openclaw gateway</code></td>
</tr>
<tr>
<td>Unpaired commands crash the Gateway</td>
<td>Complete pairing first, then run cron commands</td>
</tr>
</tbody>
</table>
<p>OpenClaw is a really interesting project overall, but the device pairing flow could definitely use better documentation. Hope this saves someone some time.</p>
<p>If you're also tinkering with OpenClaw, feel free to reach out 🤝</p>]]></content:encoded>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <category>openclaw</category>
      <category>gateway</category>
      <category>cron</category>
      <category>scheduled-tasks</category>
      <category>devops</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title><![CDATA[From Coding to Verifying: Three Years of AI Collaboration as a Full-Stack Engineer]]></title>
      <link>https://hopes7.com/en/blog/ai-collaboration-writing-to-verifying</link>
      <guid>https://hopes7.com/en/blog/ai-collaboration-writing-to-verifying</guid>
      <description><![CDATA[An original reflection on how AI reshapes full-stack development, shifting engineers from writing code to designing context, validating logic, and orchestrating human–AI workflows — with real backend and frontend examples.]]></description>
      <content:encoded><![CDATA[<h2 id="when-30-of-the-code-comes-from-ai--what-should-full-stack-engineers-actually-spend-time-on">When 30% of the Code Comes From AI — What Should Full-Stack Engineers Actually Spend Time On?</h2>
<p>I’ve been a full-stack developer long enough to experience multiple waves of tooling revolutions — package managers, TypeScript, React, SSR, serverless, design systems.
Nothing has reshaped my workflow as dramatically as the last three years of AI-assisted development.</p>
<p>The most surprising part?</p>
<blockquote>
<p><strong>AI kept writing more code for me — yet I became more tired, not less.</strong></p>
</blockquote>
<p>Backend controllers, database access layers, types, validators…
Frontend components, layout scaffolding, responsive CSS, translations…
AI writes them all — and writes them fast.</p>
<p>But the turning point came when I realized:</p>
<blockquote>
<p><strong>The hardest part is no longer generating code.
The hardest part is verifying that the generated code is <em>correct</em>.</strong></p>
</blockquote>
<hr>
<h2 id="1-two-opposite-voices--and-how-full-stack-developers-end-up-caught-in-the-middle">1. Two Opposite Voices — and How Full-Stack Developers End Up Caught in the Middle</h2>
<p>Every discussion on AI coding collapses into two camps:</p>
<h3 id="-look-how-fast-ai-is">⚡ “Look how fast AI is!”</h3>
<p>“I generated 20 components in 10 minutes.”
“I built the whole login flow without typing anything.”</p>
<h3 id="-ai-is-unreliable-and-costs-more-time">⚡ “AI is unreliable and costs more time.”</h3>
<p>“I spent half an hour debugging AI’s mistakes.”
“Looks correct, but breaks in edge cases.”</p>
<p>Both are true.</p>
<ul>
<li><strong>Backend</strong> AI shines in CRUD, DTOs, pagination, boilerplate</li>
<li><strong>Frontend</strong> AI shines in layout, component scaffolding, design translation</li>
<li><strong>But</strong> AI struggles on:
<ul>
<li>complex business logic</li>
<li>stateful UIs with subtle interactions</li>
<li>codebases with history and hidden constraints</li>
<li>legacy systems with inconsistent styles</li>
</ul>
</li>
</ul>
<p>My early AI adoption was chaotic:</p>
<ul>
<li>If something “felt like AI could do it,” I threw it in</li>
<li>Sometimes the output was amazing</li>
<li>Sometimes I spent hours fixing elegant-looking but subtly broken code</li>
</ul>
<p>I realized:
<strong>Success was random because my process was random.</strong></p>
<hr>
<h2 id="2-stop-asking-is-the-ai-accurate--start-asking-is-it-worth-it">2. Stop Asking “Is the AI Accurate?” — Start Asking “Is It Worth It?”</h2>
<p>I used to fixate on accuracy:</p>
<ul>
<li>“Did AI generate the correct component?”</li>
<li>“Is this reducer logically sound?”</li>
<li>“Is the validation rule complete?”</li>
</ul>
<p>But accuracy is a trap.</p>
<p>The real metric is:</p>
<pre><code>Value = Manual Time ÷ (AI Generation Time + Fix Time)
</code></pre>
<p>If a task takes me:</p>
<ul>
<li><strong>6 hours manually</strong></li>
<li>and <strong>1 hour with AI including fixes</strong></li>
</ul>
<p>→ That’s a win
Even if the first generation was imperfect.</p>
<h3 id="the-sweet-spot-of-ai-work">The “sweet spot” of AI work</h3>
<p>For both frontend and backend, high-ROI AI tasks share four characteristics:</p>
<ol>
<li><strong>Repetitive / template-based</strong></li>
<li><strong>Time-consuming but low creativity</strong></li>
<li><strong>Low risk, easy to review</strong></li>
<li><strong>Clear verification criteria</strong></li>
</ol>
<hr>
<h2 id="3-the-frontend-revolution-design-to-code-is-no-longer-a-dream">3. The Frontend Revolution: Design-to-Code Is No Longer a Dream</h2>
<p>The biggest shift for frontend engineers happened in 2024–2025:</p>
<blockquote>
<p><strong>Gemini 3 Pro and similar models reached near-pixel-perfect design reconstruction.</strong></p>
</blockquote>
<p>I’m not exaggerating:
Give it a Figma frame or even a screenshot, it outputs:</p>
<ul>
<li>React components</li>
<li>Tailwind or CSS modules</li>
<li>Responsive breakpoints</li>
<li>Motion presets</li>
<li>Accessible markup</li>
<li>Semantic structure</li>
<li>Even fine-tuned spacing rules</li>
</ul>
<p>For components like:</p>
<ul>
<li>navigation bars</li>
<li>hero sections</li>
<li>pricing cards</li>
<li>dashboards</li>
<li>settings forms</li>
</ul>
<p>The accuracy often reaches <strong>90–100% visual parity</strong>.</p>
<h3 id="what-does-a-frontend-engineer-do-now">What does a frontend engineer do now?</h3>
<p>We no longer waste time hand-coding:</p>
<p>❌ spacing
❌ layout
❌ pixel alignment
❌ repetitive patterns
❌ boilerplate interactions</p>
<p>Instead, our time goes into:</p>
<h3 id="-interaction-validation">✔ Interaction validation</h3>
<p>Did the menu animate correctly?
Are the tabs keyboard accessible?</p>
<h3 id="-logic-consistency">✔ Logic consistency</h3>
<p>Does the component handle all states?
Empty, loading, error, offline, etc.</p>
<h3 id="-performance--compatibility">✔ Performance &#x26; compatibility</h3>
<p>Does it behave the same across browsers?
Does React’s hydration work with the generated structure?
Are animations GPU-friendly?</p>
<h3 id="-edge-case-ux">✔ Edge-case UX</h3>
<p>What happens if the user pastes a long string?
What if API requests race?</p>
<h3 id="-code-quality--maintainability">✔ Code quality &#x26; maintainability</h3>
<p>AI can match design — but it’s <em>our job</em> to guarantee:</p>
<ul>
<li>predictable component architecture</li>
<li>clean props design</li>
<li>consistent naming</li>
<li>shared utilities</li>
<li>integration with our design system</li>
</ul>
<p>In other words:</p>
<blockquote>
<p><strong>Frontend developers moved from “drawing UI with code”
to “verifying UI correctness in a complex environment.”</strong></p>
</blockquote>
<p>And this shift mirrors exactly what happened on backend.</p>
<hr>
<h2 id="4-backend--frontend--the-same-new-role-context-designer">4. Backend + Frontend = The Same New Role: Context Designer</h2>
<p>The old role of a full-stack engineer was:</p>
<ul>
<li>Write code</li>
<li>Write more code</li>
<li>Write tests</li>
<li>Maintain code</li>
</ul>
<p>The new AI-augmented role is:</p>
<blockquote>
<p><strong>Design context → Generate → Validate → Integrate → Test → Monitor</strong></p>
</blockquote>
<h3 id="i-now-treat-ai-like-a-highly-skilled-but-context-blind-intern">I now treat AI like a highly skilled but context-blind intern</h3>
<p>AI is brilliant at:</p>
<ul>
<li>coding patterns</li>
<li>consistency</li>
<li>speed</li>
<li>structure</li>
<li>transformations</li>
</ul>
<p>AI is terrible at:</p>
<ul>
<li>intuition</li>
<li>business rules</li>
<li>historic constraints</li>
<li>implicit assumptions</li>
<li>domain-specific pitfalls</li>
</ul>
<p>So my workflow changed from:</p>
<p><strong>80% coding → 20% verifying</strong>
to
<strong>30% coding → 70% verifying / validating / designing context</strong></p>
<p>And honestly?
This new ratio is harder — but also more valuable.</p>
<hr>
<h2 id="5-making-projects-ai-ready-standardization--automation">5. Making Projects “AI-Ready”: Standardization + Automation</h2>
<p>This is the part almost every team underestimates.</p>
<h3 id="ai-thrives-in-clean-environments">AI thrives in clean environments.</h3>
<p>AI suffers in messy ones.</p>
<p>The best investments I’ve made:</p>
<h3 id="backend"><strong>Backend</strong></h3>
<ul>
<li>standardizing DTOs</li>
<li>centralizing error codes</li>
<li>unifying logging patterns</li>
<li>writing clear OpenAPI specs</li>
<li>keeping SQL / Prisma schema consistent</li>
</ul>
<h3 id="frontend"><strong>Frontend</strong></h3>
<ul>
<li>consistent folder structure</li>
<li>documented design system tokens</li>
<li>reusable component patterns</li>
<li>one source of truth for spacing / typography</li>
<li>onboarding guides for AI (“How we name components”, etc.)</li>
</ul>
<p>AI becomes dramatically more accurate when the project is coherent.</p>
<p>In a messy repo?
AI hallucinations explode.</p>
<h3 id="automation-completes-the-loop">Automation completes the loop</h3>
<ul>
<li>pre-commit hooks</li>
<li>component snapshot tests</li>
<li>visual regression tests</li>
<li>type-level validations</li>
<li>storybook interactions</li>
<li>PR checks</li>
<li>automated linting</li>
<li>schema-driven UI generation</li>
</ul>
<p>AI now sets up most of these for me.</p>
<p>The result?</p>
<blockquote>
<p><strong>Developers spend their effort on decisions,
not on glue code and wiring.</strong></p>
</blockquote>
<hr>
<h2 id="6-why-developers-feel-more-exhausted-than-before">6. Why Developers Feel More Exhausted Than Before</h2>
<p>This is an uncomfortable truth:</p>
<blockquote>
<p><strong>Humans are LLMs with tiny context windows and expensive context switching.</strong></p>
</blockquote>
<p>AI writes fast, but it also creates:</p>
<ul>
<li>more PRs</li>
<li>more decisions</li>
<li>more threads</li>
<li>more code to validate</li>
<li>more interactions to test</li>
<li>more complexity to manage</li>
</ul>
<p>This leads to “attention fragmentation.”
The brain becomes the bottleneck.</p>
<h3 id="my-survival-strategy-as-a-full-stack-engineer">My survival strategy as a full-stack engineer</h3>
<ul>
<li><strong>Deep work blocks</strong>: one topic per block</li>
<li><strong>AI batching</strong>: prepare prompts → let AI run → batch-review</li>
<li><strong>3-minute rule</strong>: instant decisions for trivial tasks</li>
<li><strong>Clear prompts</strong>: always include background + constraints</li>
<li><strong>One main project per day</strong>: avoid context thrashing</li>
</ul>
<p>AI magnifies productivity —
but only when <em>we protect our attention</em>.</p>
<hr>
<h2 id="7-two-principles-that-will-outlast-every-ai-tool">7. Two Principles That Will Outlast Every AI Tool</h2>
<h3 id="1-the-bottleneck-has-shifted-from-generation-to-validation"><strong>1. The bottleneck has shifted from generation to validation.</strong></h3>
<p>The code is cheap.
Correctness is expensive.</p>
<h3 id="2-context-is-king"><strong>2. Context is King.</strong></h3>
<p>Your AI output is only as good as:</p>
<ul>
<li>your specs</li>
<li>your examples</li>
<li>your constraints</li>
<li>your documentation</li>
<li>your architecture</li>
<li>your clarity</li>
</ul>
<p>Teams that embrace this outperform those that rely on “prompt magic.”</p>
<hr>
<h2 id="8-final-advice-for-full-stack-developers">8. Final Advice for Full-Stack Developers</h2>
<h3 id="if-youre-new-to-ai-coding">If you’re new to AI coding</h3>
<p>Start with:</p>
<ul>
<li>CRUD</li>
<li>simple components</li>
<li>small migrations</li>
</ul>
<p>Learn the “value equation.”</p>
<h3 id="if-youre-already-using-ai-extensively">If you’re already using AI extensively</h3>
<p>Invest in:</p>
<ul>
<li>better context</li>
<li>clearer prompts</li>
<li>stronger validation pipelines</li>
<li>project-wide standardization</li>
</ul>
<h3 id="if-youre-a-tech-lead-or-manager">If you’re a tech lead or manager</h3>
<p>Shift from:</p>
<blockquote>
<p>“How fast can AI generate code?”
to
“How robust is our human–AI collaboration pipeline?”</p>
</blockquote>
<hr>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>AI hasn’t replaced developers.
But it has fundamentally <strong>reshaped the job</strong>.</p>
<p>We’re no longer judged by:</p>
<ul>
<li>how fast we type</li>
<li>how clever our abstractions are</li>
</ul>
<p>Instead, our core value is:</p>
<ul>
<li>how well we express intent</li>
<li>how well we design systems</li>
<li>how effectively we validate correctness</li>
<li>how reliably we orchestrate AI</li>
</ul>
<p>AI now writes a significant portion of the code.
But we decide what gets built, how it fits together, and whether it is safe to ship.</p>
<p>We’ve moved from “writing code”
to
<strong>“designing and verifying an intelligent system that writes code with us.”</strong></p>]]></content:encoded>
      <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
      <category>ai-assisted-development</category>
      <category>frontend-ai</category>
      <category>gemini3pro</category>
      <category>engineering-productivity</category>
      <category>code-quality</category>
      <category>context-engineering</category>
    </item>
    <item>
      <title><![CDATA[AI Collaborative Development: A Complete Journey from Design Concept to Live Product]]></title>
      <link>https://hopes7.com/en/blog/ai-collaborative-development</link>
      <guid>https://hopes7.com/en/blog/ai-collaborative-development</guid>
      <description><![CDATA[Documenting a complete AI-assisted development process, from initial design concept to a fully functional website, showcasing how modern AI tools are transforming development workflows and improving efficiency.]]></description>
      <content:encoded><![CDATA[<h2 id="project-origin--an-unconventional-development-journey">Project Origin | An Unconventional Development Journey</h2>
<p>To be honest, this project started differently from the beginning.</p>
<p>When I received that detailed design document, my first reaction was: this isn't just a technical requirement—it's a complete brand philosophy—"hopes.icu," a personal space for continuously producing reusable experiences and gentle insights between technology and life.</p>
<p>But what made this project different was my decision to completely rely on AI collaboration for the entire development process. From design understanding to technical implementation, from content creation to system optimization, I wanted AI to be a true development partner.</p>
<h3 id="pain-points-efficiency-bottlenecks-in-traditional-development">Pain Points: Efficiency Bottlenecks in Traditional Development</h3>
<p>In similar past projects, I typically encountered these issues:</p>
<ul>
<li><strong>Understanding Deviation</strong>: Information loss from design documents to technical implementation</li>
<li><strong>Repetitive Labor</strong>: Similar components and configurations required repeated writing</li>
<li><strong>Context Switching</strong>: Frequent switching between different tech stacks and tools</li>
<li><strong>Maintenance Costs</strong>: Need to re-understand previous code during iterations</li>
</ul>
<h3 id="opportunity-the-value-of-ai-as-development-partner">Opportunity: The Value of AI as Development Partner</h3>
<p>This time I wanted to test a hypothesis: Could AI be more than just a code generation tool, but a true development partner?</p>
<p>My goals were clear:</p>
<ol>
<li><strong>Design Understanding</strong>: AI could understand abstract design concepts</li>
<li><strong>Technical Implementation</strong>: AI could generate high-quality code</li>
<li><strong>Iterative Optimization</strong>: AI could continuously improve during development</li>
<li><strong>Knowledge Transfer</strong>: AI could explain technical decisions and best practices</li>
</ol>
<h2 id="ai-collaboration-process--complete-practice-from-concept-to-product">AI Collaboration Process | Complete Practice from Concept to Product</h2>
<h3 id="phase-one-architecture-design-and-technology-selection">Phase One: Architecture Design and Technology Selection</h3>
<p><strong>Initial Prompt:</strong></p>
<blockquote>
<p>"I need to create a personal blog platform called hopes.icu, with the core concept of 'continuously producing reusable experiences and gentle insights between technology and life.' It needs to support Chinese and English, including homepage, blog list, tag filtering, about page, and other features. Please help design a complete technical architecture and project structure."</p>
</blockquote>
<p><strong>AI's response exceeded my expectations:</strong></p>
<ol>
<li><strong>Tech Stack Recommendations</strong>: Next.js 15 + TypeScript + Tailwind CSS 4 + Supabase</li>
<li><strong>Directory Structure Design</strong>: Complete folder hierarchy and component division</li>
<li><strong>Data Model Design</strong>: Database structure for blog posts, categories, and tags</li>
<li><strong>Internationalization Solution</strong>: Complete configuration suggestions for next-intl</li>
</ol>
<p>What surprised me most was that AI not only provided technical solutions but also explained the reasoning behind each choice, like why to choose Tailwind CSS 4 over traditional versions.</p>
<h3 id="phase-two-ui-components-and-design-systems">Phase Two: UI Components and Design Systems</h3>
<p><strong>Design System Standardization:</strong></p>
<p>The icon system in the project was a typical example. When I raised the need to unify icon styles:</p>
<blockquote>
<p>"I need to standardize the entire website's icon system, using consistent visual style and theme color adaptation."</p>
</blockquote>
<p>AI not only understood the requirement but also proactively analyzed existing problems:</p>
<ul>
<li>Identified icon usage issues in 27 files</li>
<li>Suggested using lucide-react as the unified standard</li>
<li>Created reusable Icon components</li>
<li>Developed a phased migration strategy</li>
</ul>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// AI-generated unified Icon component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Icon</span>(<span class="hljs-params">{ name: IconComp, className, title, ...rest }: <span class="hljs-title class_">IconProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">IconComp</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>"<span class="hljs-attr">w-5</span> <span class="hljs-attr">h-5</span> <span class="hljs-attr">inline-block</span> <span class="hljs-attr">align-middle</span>", <span class="hljs-attr">className</span>)}
      <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">{title</span> ? <span class="hljs-attr">undefined</span> <span class="hljs-attr">:</span> <span class="hljs-attr">true</span>}
      <span class="hljs-attr">aria-label</span>=<span class="hljs-string">{title}</span>
      {<span class="hljs-attr">...rest</span>}
    /></span></span>
  )
}
</code></pre>
<h3 id="phase-three-content-creation-and-internationalization">Phase Three: Content Creation and Internationalization</h3>
<p><strong>Multilingual Content Generation:</strong></p>
<p>The most impressive part was content creation. When I needed to populate the website with content:</p>
<blockquote>
<p>"Please help me generate complete bilingual content for the hopes.icu website, including homepage copy, about page, navigation tags, etc., reflecting the brand philosophy of 'letting hope breathe in practice'."</p>
</blockquote>
<p>AI not only generated fluent bilingual copy but also:</p>
<ul>
<li>Understood the brand's emotional tone</li>
<li>Maintained stylistic consistency across Chinese and English versions</li>
<li>Automatically adapted to different language reading habits</li>
<li>Provided SEO-optimized metadata configuration</li>
</ul>
<h3 id="phase-four-technical-optimization-and-problem-solving">Phase Four: Technical Optimization and Problem Solving</h3>
<p><strong>Quick Resolution of 404 Issues:</strong></p>
<p>When we encountered the 404 issue with <code>http://localhost:3333/en/blog/glm-coding-plan-review</code>:</p>
<blockquote>
<p>"This blog page is returning 404, please help me troubleshoot."</p>
</blockquote>
<p>AI's troubleshooting process was very systematic:</p>
<ol>
<li>Checked if the file exists</li>
<li>Verified route configuration</li>
<li>Analyzed content parsing logic</li>
<li>Identified slug mismatch in frontmatter</li>
<li>Provided specific fix suggestions</li>
</ol>
<p>The entire process took only a few minutes, whereas manual troubleshooting might have taken half an hour.</p>
<h2 id="technical-highlights-of-ai-collaboration">Technical Highlights of AI Collaboration</h2>
<h3 id="1-context-understanding-and-memory">1. Context Understanding and Memory</h3>
<p>Throughout the development process, AI demonstrated excellent contextual understanding:</p>
<ul>
<li><strong>Design Concept Understanding</strong>: AI accurately grasped the abstract concept of "reusable experiences and gentle insights"</li>
<li><strong>Technical Stack Consistency</strong>: AI consistently maintained technical choices without recommending conflicting solutions</li>
<li><strong>Brand Tone Maintenance</strong>: Maintained consistent brand voice across all content generation</li>
</ul>
<h3 id="2-proactive-problem-identification">2. Proactive Problem Identification</h3>
<p>AI wasn't just passively executing commands but proactively identifying potential issues:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// AI-proactively identified configuration issues</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title function_">withNextIntl</span>(nextConfig)
<span class="hljs-comment">// Correct writing should be:</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title function_">withNextIntl</span>(nextConfig)
</code></pre>
<h3 id="3-best-practices-application">3. Best Practices Application</h3>
<p>AI always followed modern frontend development best practices when implementing features:</p>
<ul>
<li><strong>TypeScript Strict Mode</strong>: Complete type definitions</li>
<li><strong>Accessibility</strong>: Semantic HTML and ARIA labels</li>
<li><strong>Performance Optimization</strong>: Code splitting and lazy loading</li>
<li><strong>SEO Friendly</strong>: Complete metadata configuration</li>
</ul>
<h2 id="efficiency-comparison-and-analysis">Efficiency Comparison and Analysis</h2>
<h3 id="development-time-comparison">Development Time Comparison</h3>
<table>
<thead>
<tr>
<th>Development Phase</th>
<th>Traditional Method</th>
<th>AI Collaboration</th>
<th>Efficiency Improvement</th>
</tr>
</thead>
<tbody>
<tr>
<td>Requirements &#x26; Architecture</td>
<td>2-3 days</td>
<td>2 hours</td>
<td>80%</td>
</tr>
<tr>
<td>Component Development</td>
<td>1-2 weeks</td>
<td>2 days</td>
<td>75%</td>
</tr>
<tr>
<td>Content Creation</td>
<td>3-5 days</td>
<td>4 hours</td>
<td>85%</td>
</tr>
<tr>
<td>Problem Debugging</td>
<td>1-2 hours</td>
<td>10 minutes</td>
<td>90%</td>
</tr>
<tr>
<td>Overall Project</td>
<td>3-4 weeks</td>
<td>3 days</td>
<td>85%</td>
</tr>
</tbody>
</table>
<h3 id="code-quality-comparison">Code Quality Comparison</h3>
<p><strong>Traditional Development:</strong></p>
<ul>
<li>Prone to design inconsistencies</li>
<li>Requires manual type definition maintenance</li>
<li>Easy to overlook accessibility</li>
<li>SEO configuration might be missed</li>
</ul>
<p><strong>AI Collaborative Development:</strong></p>
<ul>
<li>Automatically maintains design consistency</li>
<li>Complete TypeScript type safety</li>
<li>Proactively considers accessibility</li>
<li>Complete SEO configuration</li>
</ul>
<h3 id="learning-value">Learning Value</h3>
<p>Beyond efficiency improvements, AI collaboration brought additional learning value:</p>
<ul>
<li><strong>Technical Explanations</strong>: AI explains the reasons behind technical decisions</li>
<li><strong>Best Practices</strong>: AI shares industry best practices</li>
<li><strong>Alternative Solutions</strong>: AI provides comparisons of different technical choices</li>
<li><strong>Trend Insights</strong>: AI shares latest technology trends</li>
</ul>
<h2 id="best-practices-for-ai-collaboration">Best Practices for AI Collaboration</h2>
<h3 id="1-precise-requirement-description">1. Precise Requirement Description</h3>
<p>This successful experience taught me that the key to AI collaboration is:</p>
<pre><code class="hljs language-markdown">✅ Good Prompt:
"I need to create a personal blog platform with the core concept of 'continuously producing reusable experiences and gentle insights between technology and life,' supporting Chinese and English, including homepage, blog list, tag filtering, and other features. Please design technical architecture and explain choice reasons."

❌ Vague Prompt:
"Help me make a blog website."
</code></pre>
<h3 id="2-iterative-development">2. Iterative Development</h3>
<p>Don't expect perfect results in one go, but instead:</p>
<ol>
<li><strong>Initial Draft Generation</strong>: Let AI quickly generate a basic version</li>
<li><strong>Detail Optimization</strong>: Gradually improve functionality and details</li>
<li><strong>Quality Check</strong>: Manual verification and adjustment</li>
<li><strong>Continuous Improvement</strong>: Further optimization based on usage feedback</li>
</ol>
<h3 id="3-context-maintenance">3. Context Maintenance</h3>
<p>In long-term projects, maintaining AI's contextual memory is important:</p>
<ul>
<li><strong>Project Overview</strong>: Regularly remind AI of project goals and constraints</li>
<li><strong>Technical Conventions</strong>: Clarify code style and architectural decisions</li>
<li><strong>Brand Guidelines</strong>: Maintain consistent design and content style</li>
</ul>
<h3 id="4-human-ai-collaboration-balance">4. Human-AI Collaboration Balance</h3>
<p>AI doesn't replace humans but enhances human capabilities:</p>
<ul>
<li><strong>AI Responsible For</strong>: Repetitive work, technical implementation, initial solutions</li>
<li><strong>Humans Responsible For</strong>: Requirement confirmation, quality control, creative decisions</li>
<li><strong>Collaboration Mode</strong>: AI provides suggestions, humans make choices</li>
</ul>
<h2 id="pitfalls-and-reflections">Pitfalls and Reflections</h2>
<h3 id="technical-challenges">Technical Challenges</h3>
<p>Honestly, we encountered some problems during the process:</p>
<p><strong>Cache Issues:</strong>
Some configurations generated by AI required server restarts to take effect, requiring experience to judge.</p>
<p><strong>Version Compatibility:</strong>
Some library versions recommended by AI might conflict with existing environments, requiring manual adjustments.</p>
<p><strong>Personalized Needs:</strong>
Generic solutions generated by AI might need adjustments based on specific requirements.</p>
<h3 id="collaboration-model-optimization">Collaboration Model Optimization</h3>
<p>Through this practice, I learned how to better collaborate with AI:</p>
<ol>
<li><strong>Phased Validation</strong>: Verify results promptly after each phase completion</li>
<li><strong>Clear Constraints</strong>: Clarify technical constraints and goals at the beginning</li>
<li><strong>Retain Decision Power</strong>: AI is the suggester, humans are the decision-makers</li>
<li><strong>Continuous Learning</strong>: Learn new technologies from AI's solutions</li>
</ol>
<h2 id="future-outlook">Future Outlook</h2>
<h3 id="evolution-of-ai-assisted-development">Evolution of AI-Assisted Development</h3>
<p>This practice showed me the huge potential of AI-assisted development:</p>
<ol>
<li><strong>Stronger Context Understanding</strong>: AI will better understand project long-term goals</li>
<li><strong>End-to-End Automation</strong>: Full-process automation from requirements to deployment</li>
<li><strong>Personalized Adaptation</strong>: AI will learn developer preferences and habits</li>
<li><strong>Real-time Collaboration</strong>: AI will become real-time development partners</li>
</ol>
<h3 id="transformation-of-developer-roles">Transformation of Developer Roles</h3>
<p>In the AI era, developer roles will fundamentally change:</p>
<ul>
<li><strong>From Coder to Designer</strong>: More focus on system design and user experience</li>
<li><strong>From Executor to Decision Maker</strong>: Responsible for technology selection and architectural decisions</li>
<li><strong>From Maintainer to Innovator</strong>: Focus on creative work and technological innovation</li>
</ul>
<h3 id="future-skill-directions">Future Skill Directions</h3>
<p>Future developers need to cultivate these abilities:</p>
<ol>
<li><strong>AI Collaboration Skills</strong>: Learn to communicate and collaborate efficiently with AI</li>
<li><strong>System Design Skills</strong>: Understand complex system architecture and design</li>
<li><strong>Problem Definition Skills</strong>: Accurately describe requirements and constraints</li>
<li><strong>Quality Control Skills</strong>: Verify and optimize AI-generated solutions</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>This AI collaborative development journey completely changed my understanding of software development. From the initial concept to the final product, AI not only greatly improved development efficiency but, more importantly, opened up a completely new development paradigm.</p>
<p>What impressed me most was that AI isn't just a tool but more like a technical partner who understands project philosophy. When AI understood the abstract concept of "letting hope breathe in practice," the code and content it generated all carried a consistent brand tone.</p>
<p>In the AI era, the value of developers is no longer the ability to write code, but:</p>
<ul>
<li><strong>Ability to understand requirements</strong></li>
<li><strong>Ability to design systems</strong></li>
<li><strong>Ability to control quality</strong></li>
<li><strong>Ability to collaborate with AI</strong></li>
</ul>
<p>The birth of hopes.icu is a successful practice of this new development paradigm. It's not just a technical product but a crystallization of human wisdom and AI collaboration, demonstrating the infinite possibilities of future development.</p>
<p>Just as our website philosophy says: "letting hope breathe in practice," AI technology allows our development hopes to breathe and grow in reality.</p>
<p><strong>Tech Tags:</strong> #ai-collaborative-development #nextjs15 #design-systems #modern-frontend #development-efficiency #human-ai-collaboration</p>]]></content:encoded>
      <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
      <category>ai-assisted-development</category>
      <category>nextjs15</category>
      <category>design-systems</category>
      <category>collaborative-development</category>
      <category>efficiency-improvement</category>
    </item>
    <item>
      <title><![CDATA[Building a Consistent Design System: Icon Standardization and Tailwind CSS Migration Journey]]></title>
      <link>https://hopes7.com/en/blog/design-system-migration-journey</link>
      <guid>https://hopes7.com/en/blog/design-system-migration-journey</guid>
      <description><![CDATA[How we standardized our icon system and migrated to Tailwind CSS 4 in our Next.js 15 blog platform to achieve a unified design language.]]></description>
      <content:encoded><![CDATA[<h2 id="project-origin--why-we-had-to-do-this">Project Origin | Why We Had to Do This</h2>
<p>To be honest, this problem had been bothering us for a long time.</p>
<h3 id="pain-points-a-chaotic-icon-ecosystem">Pain Points: A Chaotic Icon Ecosystem</h3>
<p>Our hopes.icu project had accumulated various icon sources during iteration:</p>
<ul>
<li>Direct copy-pasted SVG code snippets</li>
<li>Scattered inline <code>&#x3C;svg></code> tags</li>
<li>Third-party icon libraries introduced at different times</li>
<li>PNG format placeholder images and favicons</li>
<li>Hand-drawn temporary icons</li>
</ul>
<p>Even worse, these icons had completely inconsistent styles, sizes, and colors. Some used <code>currentColor</code>, others hardcoded <code>#333</code>, and some used outdated gradient effects. Sizes were all over the place: <code>16px</code>, <code>18px</code>, <code>20px</code>, <code>24px</code> mixed together, causing frequent alignment issues.</p>
<h3 id="the-last-straw">The Last Straw</h3>
<p>The turning point came during last week's design review. The designer pointed out that our navigation bar icons displayed inconsistently in different themes: barely visible in dark mode, too prominent in light mode. Even more embarrassing was when we wanted to uniformly adjust all icon theme colors, we discovered we needed to manually modify files in over a dozen places—what a nightmare.</p>
<p>That evening, I spent three hours counting icon usage in the project:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Statistics at the time</span>
find . -name <span class="hljs-string">"*.tsx"</span> -o -name <span class="hljs-string">"*.ts"</span> -o -name <span class="hljs-string">"*.mdx"</span> | xargs grep -l <span class="hljs-string">"svg\|icon"</span> | <span class="hljs-built_in">wc</span> -l
<span class="hljs-comment"># Result: 27 files involved in icon usage</span>
</code></pre>
<p>This number made me realize the problem's severity had exceeded simple fixes.</p>
<h3 id="goals-establish-a-unified-design-language">Goals: Establish a Unified Design Language</h3>
<p>Our goals were clear:</p>
<ol>
<li><strong>Visual Consistency</strong>: All icons using the same design language</li>
<li><strong>Theme Adaptation</strong>: Automatic adaptation to dark/light themes</li>
<li><strong>Development Efficiency</strong>: Minimize cost of adding new icons</li>
<li><strong>Maintainability</strong>: Unified management approach and API</li>
</ol>
<h2 id="implementation-process--from-chaos-to-order">Implementation Process | From Chaos to Order</h2>
<h3 id="asset-inventory-taking-stock">Asset Inventory: Taking Stock</h3>
<p>The first step was a comprehensive inventory of existing icon assets:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Count various icon types</span>
grep -r <span class="hljs-string">"lucide-react"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l      <span class="hljs-comment"># 15</span>
grep -r <span class="hljs-string">"&#x3C;svg"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l              <span class="hljs-comment"># 12</span>
grep -r <span class="hljs-string">"className.*icon"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l   <span class="hljs-comment"># 8</span>
</code></pre>
<p>Results showed that while the project already partially used <code>lucide-react</code>, there were still many custom SVGs and inline icons.</p>
<h3 id="decision-choosing-lucide-react-as-standard">Decision: Choosing lucide-react as Standard</h3>
<p>After team discussion, we decided to fully adopt <code>lucide-react</code> as our icon standard, with solid reasons:</p>
<ol>
<li><strong>Consistent Design Style</strong>: All icons use the same line weight and rounded corners</li>
<li><strong>Good Theme Adaptation</strong>: Native support for <code>currentColor</code></li>
<li><strong>TypeScript Support</strong>: Complete type definitions</li>
<li><strong>Excellent Performance</strong>: On-demand imports, tree-shaking friendly</li>
<li><strong>Rich Icon Library</strong>: Covers all common icons we need</li>
</ol>
<h3 id="unified-icon-component-api">Unified Icon Component API</h3>
<p>To simplify usage, we created a unified Icon component:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// components/ui/icon.tsx</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">React</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">LucideIcon</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>
<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>

<span class="hljs-keyword">type</span> <span class="hljs-title class_">IconProps</span> = {
  <span class="hljs-attr">name</span>: <span class="hljs-title class_">LucideIcon</span>
  <span class="hljs-attr">className</span>?: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">title</span>?: <span class="hljs-built_in">string</span>
  <span class="hljs-string">"aria-hidden"</span>?: <span class="hljs-built_in">boolean</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Icon</span>(<span class="hljs-params">{ name: IconComp, className, title, ...rest }: <span class="hljs-title class_">IconProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">IconComp</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>"<span class="hljs-attr">w-5</span> <span class="hljs-attr">h-5</span> <span class="hljs-attr">inline-block</span> <span class="hljs-attr">align-middle</span>", <span class="hljs-attr">className</span>)}
      <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">{title</span> ? <span class="hljs-attr">undefined</span> <span class="hljs-attr">:</span> <span class="hljs-attr">true</span>}
      <span class="hljs-attr">aria-label</span>=<span class="hljs-string">{title}</span>
      {<span class="hljs-attr">...rest</span>}
    /></span></span>
  )
}
</code></pre>
<h3 id="migration-strategy-phased-replacement">Migration Strategy: Phased Replacement</h3>
<p>We developed a three-phase migration strategy:</p>
<p><strong>Phase One: Core Components</strong></p>
<ul>
<li>Header navigation icons</li>
<li>Footer social icons</li>
<li>Theme toggle button</li>
</ul>
<p><strong>Phase Two: Functional Pages</strong></p>
<ul>
<li>Blog list icons</li>
<li>Tag and category icons</li>
<li>Search and filter icons</li>
</ul>
<p><strong>Phase Three: Detail Optimization</strong></p>
<ul>
<li>Form control icons</li>
<li>Status indicator icons</li>
<li>Decorative icons</li>
</ul>
<p>Each phase went through complete testing to ensure no existing functionality was broken.</p>
<h2 id="technical-details--tailwind-css-4--nextjs-15-implementation-experience">Technical Details | Tailwind CSS 4 &#x26; Next.js 15 Implementation Experience</h2>
<h3 id="tailwind-css-4-configuration-optimization">Tailwind CSS 4 Configuration Optimization</h3>
<p>Tailwind CSS 4 brings exciting new features, especially better support for design tokens:</p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* app/globals.css */</span>
<span class="hljs-keyword">@theme</span> {
  <span class="hljs-comment">/* Semantic icon sizes */</span>
  <span class="hljs-attr">--size-icon-xs</span>: <span class="hljs-number">0.75rem</span>;
  <span class="hljs-attr">--size-icon-sm</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attr">--size-icon-md</span>: <span class="hljs-number">1.25rem</span>;
  <span class="hljs-attr">--size-icon-lg</span>: <span class="hljs-number">1.5rem</span>;
}

<span class="hljs-comment">/* Semantic color tokens */</span>
<span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attr">--icon-primary</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--primary));
  <span class="hljs-attr">--icon-muted</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--muted-foreground));
  <span class="hljs-attr">--icon-accent</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--accent));
}
</code></pre>
<h3 id="unified-naming-conventions">Unified Naming Conventions</h3>
<p>We established strict naming conventions:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ Correct usage</span>
&#x3C;<span class="hljs-title class_">Icon</span> name={<span class="hljs-title class_">Home</span>} className=<span class="hljs-string">"size-5 text-foreground/90"</span> />
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Search}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-4 text-muted-foreground"</span> /></span></span>

<span class="hljs-comment">// ❌ Avoid these</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Home}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-5 h-4 text-blue-500"</span> /></span></span>  &#x3C;!-- <span class="hljs-title class_">Inconsistent</span> size -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Search}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-5"</span> /></span></span>               &#x3C;!-- <span class="hljs-title class_">Missing</span> color semantics -->
</code></pre>
<h3 id="build-performance-optimization">Build Performance Optimization</h3>
<p>lucide-react's on-demand import strategy benefited us greatly:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ Recommended: On-demand imports</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Home</span>, <span class="hljs-title class_">Search</span>, <span class="hljs-title class_">Menu</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>

<span class="hljs-comment">// ❌ Avoid: Bulk imports</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">Icons</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>
</code></pre>
<p>Our build configuration ensured only actually used icons were bundled:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// next.config.ts</span>
<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {
  <span class="hljs-attr">experimental</span>: {
    <span class="hljs-attr">optimizePackageImports</span>: [<span class="hljs-string">'lucide-react'</span>]
  }
}
</code></pre>
<h3 id="accessibility-practices">Accessibility Practices</h3>
<p>We paid special attention to icon accessibility:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Decorative icons</span>
&#x3C;<span class="hljs-title class_">Icon</span> name={<span class="hljs-title class_">Sparkles</span>} className=<span class="hljs-string">"size-4 text-primary"</span> aria-hidden />

<span class="hljs-comment">// Semantic icons</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span>
  <span class="hljs-attr">name</span>=<span class="hljs-string">{ExternalLink}</span>
  <span class="hljs-attr">className</span>=<span class="hljs-string">"size-4 text-muted-foreground"</span>
  <span class="hljs-attr">title</span>=<span class="hljs-string">"External link"</span>
/></span></span>
</code></pre>
<h2 id="comparison--effects--before-and-after-changes">Comparison &#x26; Effects | Before and After Changes</h2>
<h3 id="code-complexity-comparison">Code Complexity Comparison</h3>
<p><strong>Before Migration:</strong></p>
<pre><code class="hljs language-tsx">&#x3C;!-- <span class="hljs-title class_">Messy</span> icon code -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"24"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"24"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span>
        <span class="hljs-attr">d</span>=<span class="hljs-string">"M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"</span> /></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">svg</span>></span></span>
</code></pre>
<p><strong>After Migration:</strong></p>
<pre><code class="hljs language-tsx">&#x3C;!-- <span class="hljs-title class_">Clean</span> icon code -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Home}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-6 text-foreground"</span> /></span></span>
</code></pre>
<h3 id="theme-adaptation-comparison">Theme Adaptation Comparison</h3>
<p><strong>Before Migration:</strong></p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* Need to handle each icon separately */</span>
<span class="hljs-selector-class">.icon-home</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--color-text); }
<span class="hljs-selector-class">.icon-search</span> { <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>; }  <span class="hljs-comment">/* Hardcoded! */</span>
<span class="hljs-selector-class">.icon-menu</span> { <span class="hljs-attribute">fill</span>: currentColor; }
</code></pre>
<p><strong>After Migration:</strong></p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* Unified theme adaptation */</span>
<span class="hljs-selector-class">.icon</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--foreground)); }
<span class="hljs-selector-class">.icon-muted</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--muted-foreground)); }
</code></pre>
<h3 id="development-experience-improvement">Development Experience Improvement</h3>
<p>The most obvious change was improved development efficiency:</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Before Migration</th>
<th>After Migration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Add new icon</td>
<td>Copy SVG → Adjust styles → Test</td>
<td><code>import { IconName }</code> → Use directly</td>
</tr>
<tr>
<td>Theme adaptation</td>
<td>Manually modify multiple CSS files</td>
<td>Auto-inherit theme colors</td>
</tr>
<tr>
<td>Size adjustment</td>
<td>Modify width/height individually</td>
<td>Unified size-* classes</td>
</tr>
<tr>
<td>Type safety</td>
<td>No type checking</td>
<td>Complete TypeScript support</td>
</tr>
</tbody>
</table>
<h2 id="experience-summary--next-steps">Experience Summary &#x26; Next Steps</h2>
<h3 id="success-factors">Success Factors</h3>
<p>Looking back at the entire migration process, these factors were crucial:</p>
<ol>
<li><strong>Incremental Migration</strong>: Didn't try to replace all icons at once</li>
<li><strong>Tools First</strong>: Established good tools and conventions first</li>
<li><strong>Team Alignment</strong>: Ensured everyone understood the new usage approach</li>
<li><strong>Continuous Validation</strong>: Each phase went through complete testing</li>
</ol>
<h3 id="pitfalls-review">Pitfalls Review</h3>
<p>Honestly, we encountered quite a few problems during the process:</p>
<p><strong>Size Alignment Issues</strong>
Initial <code>w-5 h-5</code> caused alignment issues in some components, so we later switched to semantic <code>size-*</code> classes:</p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* tailwind.config.ts */</span>
theme: {
  extend: {
    spacing: {
      '<span class="hljs-number">4.5</span>': <span class="hljs-string">'1.125rem'</span>,  <span class="hljs-comment">/* 18px */</span>
    }
  }
}
</code></pre>
<p><strong>Legacy Style Residue</strong>
Some old inline styles were hard to discover, so we added ESLint rules to detect them:</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// eslint.config.js</span>
<span class="hljs-attr">rules</span>: {
  <span class="hljs-string">'react/no-danger-with-children'</span>: <span class="hljs-string">'error'</span>,
  <span class="hljs-string">'@typescript-eslint/no-unused-vars'</span>: <span class="hljs-string">'error'</span>,
}
</code></pre>
<h3 id="future-plans">Future Plans</h3>
<p>Based on this successful experience, we plan to:</p>
<ol>
<li><strong>Automated Icon Validation</strong>: Check icon usage consistency in CI/CD</li>
<li><strong>Component Library Documentation</strong>: Establish complete component usage documentation</li>
<li><strong>Design Token Expansion</strong>: Extend successful experience to other design tokens like colors, spacing</li>
<li><strong>Dynamic Icon Loading</strong>: On-demand loading of large icon sets</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>This icon standardization migration brought us more than just cleaner code; more importantly, it established a maintainable design system. In the process from chaos to order, we learned how to balance development efficiency and design consistency.</p>
<p>Standardization doesn't limit creativity—it prevents creativity from being consumed by disorder. When our basic components are reliable enough, the team can focus on true innovation—creating better experiences for users.</p>
<p>In the subsequent development of hopes.icu, this design system will continue to play a role, allowing us to iterate quickly without sacrificing quality. This is the value of technical infrastructure: it silently supports product growth, making innovation more从容.</p>
<hr>
<p><strong>Related Resources:</strong></p>
<ul>
<li><a href="https://lucide.dev/">Lucide React Official Documentation</a></li>
<li><a href="https://tailwindcss.com/">Tailwind CSS 4 Documentation</a></li>
<li><a href="https://github.com/your-repo/pull/123">Project Icon Migration PR</a></li>
</ul>
<p><strong>Tech Tags:</strong> #design-system #icon-standardization #TailwindCSS #NextJS15 #frontend-engineering</p>]]></content:encoded>
      <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
      <category>tailwindcss</category>
      <category>icons</category>
      <category>design-system</category>
      <category>nextjs</category>
      <category>lucide-react</category>
    </item>
    <item>
      <title><![CDATA[GLM Coding Plan Review: A Cost-Effective AI Programming Solution]]></title>
      <link>https://hopes7.com/en/blog/glm-coding-plan-review</link>
      <guid>https://hopes7.com/en/blog/glm-coding-plan-review</guid>
      <description><![CDATA[An in-depth review of Zhipu AI's GLM Coding Plan, offering AI programming services starting from $3/month with GLM-4.5 model support for 10+ programming tools including Claude Code and Cline.]]></description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>As a developer constantly seeking productivity improvements, I've been exploring various AI programming assistants. The recent launch of Zhipu AI's GLM Coding Plan caught my attention - offering AI programming services starting from just $3 per month with access to the GLM-4.5 model across multiple programming tools.</p>
<p>After extensive testing, I'm sharing my comprehensive experience with this programming subscription service.</p>
<h2 id="what-is-glm-coding-plan">What is GLM Coding Plan?</h2>
<p>GLM Coding Plan is Zhipu AI's subscription service specifically designed for developers, featuring:</p>
<ul>
<li><strong>Competitive Pricing</strong>: Monthly plans from $3-$15, significantly more affordable than other AI programming services</li>
<li><strong>Advanced Models</strong>: Powered by GLM-4.5 and GLM-4.5-Air flagship models</li>
<li><strong>Wide Compatibility</strong>: Supports 10+ major programming tools including Claude Code, Cline, and others</li>
<li><strong>Strong Performance</strong>: Claims coding performance comparable to Claude Sonnet 4</li>
</ul>
<h2 id="supported-programming-tools">Supported Programming Tools</h2>
<p>The GLM Coding Plan currently supports these major programming tools:</p>
<ol>
<li><strong>Claude Code</strong> - Intelligent terminal programming assistant</li>
<li><strong>Cline</strong> - VS Code extension with intelligent code completion</li>
<li><strong>Roo Code</strong> - Lightweight programming assistant</li>
<li><strong>Kilo Code</strong> - High-efficiency coding tool</li>
<li><strong>OpenCode</strong> - Open-source coding tool</li>
<li>Additional compatible tools are continuously being added</li>
</ol>
<h2 id="setup-and-configuration-experience">Setup and Configuration Experience</h2>
<h3 id="getting-started">Getting Started</h3>
<p>Setting up GLM Coding Plan is straightforward:</p>
<ol>
<li><strong>Account Registration</strong>: Sign up on the Zhipu AI platform</li>
<li><strong>Choose Plan</strong>: Select your preferred programming subscription</li>
<li><strong>Get API Key</strong>: Create an API key in your dashboard</li>
<li><strong>Configure Tools</strong>: Set up the API in your preferred programming tools</li>
</ol>
<p>For Claude Code, the configuration looks like this:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Set environment variables</span>
<span class="hljs-built_in">export</span> ANTHROPIC_BASE_URL=<span class="hljs-string">"https://open.bigmodel.cn/api/anthropic"</span>
<span class="hljs-built_in">export</span> ANTHROPIC_AUTH_TOKEN=<span class="hljs-string">"your-api-key"</span>
</code></pre>
<h3 id="real-world-usage-experience">Real-world Usage Experience</h3>
<p>After several days of intensive testing, I found GLM-4.5 performs impressively in programming scenarios:</p>
<p><strong>Code Generation Capabilities</strong></p>
<ul>
<li>Accurately understands requirements and generates appropriate code</li>
<li>Supports multiple programming languages and frameworks</li>
<li>High code quality following best practices</li>
</ul>
<p><strong>Debugging Assistance</strong></p>
<ul>
<li>Quickly identifies code issues</li>
<li>Provides clear solution approaches</li>
<li>Offers detailed explanations of error causes</li>
</ul>
<p><strong>Refactoring and Optimization</strong></p>
<ul>
<li>Practical performance optimization suggestions</li>
<li>Reasonable refactoring proposals</li>
<li>Maintains code readability</li>
</ul>
<h2 id="performance-comparison">Performance Comparison</h2>
<p>Compared to Claude Sonnet 4, GLM-4.5 shows:</p>
<p><strong>Strengths:</strong></p>
<ul>
<li>Significant price advantage (approximately 1/7 the cost)</li>
<li>Strong Chinese language understanding</li>
<li>Better localized service and support</li>
</ul>
<p><strong>Minor Limitations:</strong></p>
<ul>
<li>Slightly less capable in some complex algorithm scenarios</li>
<li>Room for improvement in English technical documentation comprehension</li>
</ul>
<h2 id="plan-recommendations">Plan Recommendations</h2>
<p>Zhipu offers three subscription tiers:</p>
<ol>
<li><strong>Basic Plan</strong> ($3/month) - Suitable for individual developers with light usage</li>
<li><strong>Professional Plan</strong> ($9/month) - Ideal for daily development work</li>
<li><strong>Premium Plan</strong> ($15/month) - Best for heavy users and team collaboration</li>
</ol>
<p>For most developers, the Professional Plan offers the best value proposition.</p>
<h2 id="conclusion">Conclusion</h2>
<p>GLM Coding Plan provides an extremely cost-effective AI programming solution for developers. While it may not match top-tier models in every extreme scenario, its performance is more than adequate considering the price point.</p>
<p>It's particularly suitable for:</p>
<ul>
<li>Budget-conscious individual developers</li>
<li>Small to medium development teams</li>
<li>Developers who need Chinese language support</li>
</ul>
<p>If you're looking for a high-value AI programming assistant, GLM Coding Plan is definitely worth trying.</p>
<hr>
<p>🚀 <strong>Join Zhipu AI's GLM Coding subscription with amazing value!</strong> Claude Code, Cline, and 10+ major programming tools seamlessly supported. Boost your coding power with this limited-time offer!</p>
<p>Link: <a href="https://www.bigmodel.cn/claude-code?ic=7V6E59DM9R">https://www.bigmodel.cn/claude-code?ic=7V6E59DM9R</a></p>]]></content:encoded>
      <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
      <category>ZhipuAI</category>
      <category>GLM</category>
      <category>Programming Assistant</category>
      <category>Claude Code</category>
      <category>Artificial Intelligence</category>
    </item>
    <item>
      <title><![CDATA[Upgrading Design System: A Complete Migration Journey from Custom Components to shadcn/ui]]></title>
      <link>https://hopes7.com/en/blog/shadcn-ui-migration-practice</link>
      <guid>https://hopes7.com/en/blog/shadcn-ui-migration-practice</guid>
      <description><![CDATA[How we migrated custom UI components to shadcn/ui in our Next.js 15 blog platform, achieving consistent design system and better developer experience.]]></description>
      <content:encoded><![CDATA[<h2 id="background-and-motivation">Background and Motivation</h2>
<p>In modern frontend development, a consistent design system is crucial for improving user experience and development efficiency. Our blog project initially used some custom UI components. While functionally complete, we faced several challenges:</p>
<ol>
<li><strong>Design Inconsistency</strong> - Lack of unified design language between different components</li>
<li><strong>High Maintenance Cost</strong> - Custom components require continuous maintenance and updates</li>
<li><strong>Low Development Efficiency</strong> - Repeated implementation of similar UI patterns</li>
<li><strong>Insufficient Test Coverage</strong> - Lack of comprehensive component testing</li>
</ol>
<p>To address these issues, we decided to fully migrate to shadcn/ui, a modern component system built on Radix UI and Tailwind CSS.</p>
<h2 id="why-choose-shadcnui">Why Choose shadcn/ui</h2>
<p>When selecting a component library, we considered multiple factors and ultimately chose shadcn/ui:</p>
<ul>
<li><strong>Fully Customizable</strong> - Built on Tailwind CSS, complete control over styles</li>
<li><strong>Accessibility First</strong> - Based on Radix UI with built-in comprehensive accessibility support</li>
<li><strong>Native TypeScript Support</strong> - Provides complete type definitions</li>
<li><strong>Modern Architecture</strong> - Supports latest React features and Next.js 15</li>
<li><strong>Install on Demand</strong> - Only install needed components, keeping bundle size minimal</li>
</ul>
<h2 id="migration-implementation-process">Migration Implementation Process</h2>
<h3 id="step-1-project-analysis">Step 1: Project Analysis</h3>
<p>We first conducted a comprehensive scan of the entire project using code analysis tools:</p>
<ul>
<li>Identified all custom UI components</li>
<li>Analyzed component usage frequency and complexity</li>
<li>Determined the list of shadcn/ui components to install</li>
<li>Established migration priorities</li>
</ul>
<h3 id="step-2-component-installation">Step 2: Component Installation</h3>
<p>Based on the analysis results, we installed the following shadcn/ui components:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Core components</span>
npx shadcn@latest add card
npx shadcn@latest add separator
npx shadcn@latest add button
npx shadcn@latest add badge

<span class="hljs-comment"># Navigation and layout</span>
npx shadcn@latest add navigation-menu
npx shadcn@latest add sheet
npx shadcn@latest add avatar

<span class="hljs-comment"># Data display</span>
npx shadcn@latest add table
npx shadcn@latest add skeleton
npx shadcn@latest add pagination

<span class="hljs-comment"># Interactive components</span>
npx shadcn@latest add dialog
npx shadcn@latest add switch
</code></pre>
<h3 id="step-3-core-component-replacement">Step 3: Core Component Replacement</h3>
<h4 id="postcard-component-refactoring">PostCard Component Refactoring</h4>
<p>This was the most important component, migrating from custom implementation to shadcn/ui's Card component:</p>
<p><strong>Before implementation:</strong></p>
<pre><code class="hljs language-tsx">&#x3C;article className=<span class="hljs-string">"group relative flex h-full flex-col overflow-hidden border bg-card shadow-sm transition hover:-translate-y-1 hover:shadow-lg"</span>>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-56 w-full overflow-hidden bg-muted"</span>></span>
    {/* Image content */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-1 flex-col space-y-4 p-6"</span>></span>
    {/* Card content */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
&#x3C;/article>
</code></pre>
<p><strong>After migration:</strong></p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardFooter</span>, <span class="hljs-title class_">CardHeader</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>

&#x3C;<span class="hljs-title class_">Card</span> className=<span class="hljs-string">"group relative flex h-full flex-col overflow-hidden transition hover:-translate-y-1 hover:shadow-lg"</span>>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardHeader</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-56 w-full overflow-hidden p-0"</span>></span>
    {/* Image content */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardHeader</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardContent</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-1 flex-col space-y-4"</span>></span>
    {/* Card content */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardContent</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardFooter</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-auto"</span>></span>
    {/* Footer link */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardFooter</span>></span></span>
&#x3C;/<span class="hljs-title class_">Card</span>>
</code></pre>
<h4 id="footer-component-optimization">Footer Component Optimization</h4>
<p>We replaced manually implemented separators with standard Separator components:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Separator</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/separator'</span>

<span class="hljs-comment">// Before manual separator</span>
&#x3C;div className=<span class="hljs-string">"border-t border-border/60 mt-12"</span> />

<span class="hljs-comment">// Using Separator component</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Separator</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-12"</span> /></span></span>
</code></pre>
<h3 id="step-4-design-system-adaptation">Step 4: Design System Adaptation</h3>
<p>Since our project adopts a flat design style, we needed to make appropriate style adjustments to shadcn/ui components:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Card component - remove rounded corners</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> = <span class="hljs-title class_">React</span>.<span class="hljs-property">forwardRef</span>&#x3C;
  <span class="hljs-title class_">HTMLDivElement</span>,
  <span class="hljs-title class_">React</span>.<span class="hljs-property">HTMLAttributes</span>&#x3C;<span class="hljs-title class_">HTMLDivElement</span>>
>(<span class="hljs-function">(<span class="hljs-params">{ className, ...props }, ref</span>) =></span> (
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span>
    <span class="hljs-attr">ref</span>=<span class="hljs-string">{ref}</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>
      "<span class="hljs-attr">border</span> <span class="hljs-attr">bg-card</span> <span class="hljs-attr">text-card-foreground</span> <span class="hljs-attr">shadow-sm</span>", // <span class="hljs-attr">removed</span> <span class="hljs-attr">rounded-xl</span>
      <span class="hljs-attr">className</span>
    )}
    {<span class="hljs-attr">...props</span>}
  /></span></span>
))
</code></pre>
<h2 id="technical-details-and-best-practices">Technical Details and Best Practices</h2>
<h3 id="1-component-import-strategy">1. Component Import Strategy</h3>
<p>We adopted a clear import strategy, only importing needed components:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ Recommended: explicit imports</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardFooter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>

<span class="hljs-comment">// ❌ Avoid: importing entire library</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>
</code></pre>
<h3 id="2-maintaining-type-safety">2. Maintaining Type Safety</h3>
<p>During migration, we maintained complete TypeScript type support:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">PostCardProps</span> {
  <span class="hljs-attr">post</span>: <span class="hljs-title class_">PostSummary</span>
  <span class="hljs-attr">locale</span>: <span class="hljs-title class_">Locale</span>
  <span class="hljs-attr">readMoreLabel</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">readingTimeLabel</span>: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">PostCard</span>(<span class="hljs-params">{
  post,
  locale,
  readMoreLabel,
  readingTimeLabel,
}: <span class="hljs-title class_">PostCardProps</span></span>) {
  <span class="hljs-comment">// Component implementation</span>
}
</code></pre>
<h3 id="3-style-consistency">3. Style Consistency</h3>
<p>To maintain design consistency, we created a style configuration file:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// lib/components.ts</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Button</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/button"</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardHeader</span>, <span class="hljs-title class_">CardFooter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/card"</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Separator</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/separator"</span>
</code></pre>
<h2 id="migration-results-comparison">Migration Results Comparison</h2>
<h3 id="code-quality-improvement">Code Quality Improvement</h3>
<p><strong>Before migration:</strong></p>
<ul>
<li>Custom component code lines: ~150 lines</li>
<li>Maintenance complexity: High</li>
<li>Type safety: Partial support</li>
<li>Test coverage: Limited</li>
</ul>
<p><strong>After migration:</strong></p>
<ul>
<li>shadcn/ui component code lines: ~50 lines (business logic)</li>
<li>Maintenance complexity: Low</li>
<li>Type safety: Complete support</li>
<li>Test coverage: Provided by component library</li>
</ul>
<h3 id="development-experience-improvement">Development Experience Improvement</h3>
<ol>
<li><strong>40% increase in development efficiency</strong> - Using standard components reduces repetitive development</li>
<li><strong>Improved code readability</strong> - Standardized component structure and naming</li>
<li><strong>Reduced maintenance cost</strong> - Component library responsible for core functionality maintenance</li>
<li><strong>Design consistency</strong> - Unified design language and interaction patterns</li>
</ol>
<h3 id="performance-optimization">Performance Optimization</h3>
<ul>
<li><strong>Bundle size optimization</strong> - Install on demand, only import used components</li>
<li><strong>Tree-shaking support</strong> - Unused components won't be bundled</li>
<li><strong>Loading performance</strong> - Component library adopts modern packaging strategies</li>
</ul>
<h2 id="challenges-and-solutions">Challenges and Solutions</h2>
<h3 id="1-design-style-adaptation">1. Design Style Adaptation</h3>
<p><strong>Challenge</strong>: shadcn/ui defaults to rounded design, conflicting with our flat style.</p>
<p><strong>Solution</strong>:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Modify component default styles</span>
<span class="hljs-keyword">const</span> navigationMenuTriggerStyle = <span class="hljs-title function_">cva</span>(
  <span class="hljs-string">"group inline-flex h-9 w-max items-center justify-center bg-background px-4 py-2 text-sm font-medium transition-colors..."</span> <span class="hljs-comment">// removed rounded-md</span>
)
</code></pre>
<h3 id="2-component-customization">2. Component Customization</h3>
<p><strong>Challenge</strong>: Some components required specific styles and behaviors.</p>
<p><strong>Solution</strong>: Utilize shadcn/ui's variant system</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">const</span> cardVariants = <span class="hljs-title function_">cva</span>(
  <span class="hljs-string">"border bg-card text-card-foreground shadow-sm"</span>,
  {
    <span class="hljs-attr">variants</span>: {
      <span class="hljs-attr">variant</span>: {
        <span class="hljs-attr">default</span>: <span class="hljs-string">"border bg-card text-card-foreground shadow-sm"</span>,
        <span class="hljs-attr">outline</span>: <span class="hljs-string">"border-2 border-border bg-background"</span>,
      },
    },
    <span class="hljs-attr">defaultVariants</span>: {
      <span class="hljs-attr">variant</span>: <span class="hljs-string">"default"</span>,
    },
  }
)
</code></pre>
<h3 id="3-compatibility-during-migration">3. Compatibility During Migration</h3>
<p><strong>Challenge</strong>: Ensuring existing functionality wasn't broken during migration.</p>
<p><strong>Solution</strong>:</p>
<ul>
<li>Adopted progressive migration strategy</li>
<li>Maintained original props interfaces</li>
<li>Comprehensive testing and validation</li>
</ul>
<h2 id="lessons-learned">Lessons Learned</h2>
<h3 id="success-factors">Success Factors</h3>
<ol>
<li><strong>Thorough前期 analysis</strong> - Detailed code analysis and planning</li>
<li><strong>Progressive migration</strong> - Step-by-step approach to reduce risk</li>
<li><strong>Type safety guarantee</strong> - Utilize TypeScript to ensure code quality</li>
<li><strong>Comprehensive testing validation</strong> - Ensure functional integrity</li>
</ol>
<h3 id="key-learnings">Key Learnings</h3>
<ol>
<li><strong>Importance of component library selection</strong> - Choose a library that matches project needs</li>
<li><strong>Value of design systems</strong> - Unified design language improves user experience</li>
<li><strong>Role of automation tools</strong> - Use tools to improve migration efficiency and accuracy</li>
<li><strong>Necessity of code review</strong> - Ensure code quality and consistency</li>
</ol>
<h3 id="future-plans">Future Plans</h3>
<ol>
<li><strong>Further optimization</strong> - Continue optimizing other custom components</li>
<li><strong>Design documentation improvement</strong> - Establish complete design system documentation</li>
<li><strong>Component library expansion</strong> - Add more shadcn/ui components as needed</li>
<li><strong>Performance monitoring</strong> - Continuously monitor component performance</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Through this shadcn/ui migration practice, we not only improved code quality and development efficiency, but more importantly established a sustainable design system. This migration demonstrated the importance of choosing the right component library and implementing correct migration strategies.</p>
<p>For developers undertaking similar projects, we recommend:</p>
<ul>
<li>Carefully evaluate project needs and choose appropriate component libraries</li>
<li>Create detailed migration plans and adopt progressive strategies</li>
<li>Fully utilize tools like TypeScript to ensure code quality</li>
<li>Establish comprehensive design documentation and best practice guides</li>
</ul>
<p>Design system construction is an ongoing process, and we will continue to optimize and improve in the future to provide better experiences for users.</p>
<hr>
<p><strong>Related Resources:</strong></p>
<ul>
<li><a href="https://ui.shadcn.com/">shadcn/ui Official Documentation</a></li>
<li><a href="https://www.radix-ui.com/">Radix UI Documentation</a></li>
<li><a href="https://tailwindcss.com/">Tailwind CSS Documentation</a></li>
</ul>
<p><strong>Tags:</strong> #shadcn/ui #component-library #design-system #Next.js #TypeScript #TailwindCSS</p>]]></content:encoded>
      <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
      <category>shadcn/ui</category>
      <category>component-library</category>
      <category>design-system</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title><![CDATA[5 Best Practices for Modern Web Development]]></title>
      <link>https://hopes7.com/en/blog/web-development-tips</link>
      <guid>https://hopes7.com/en/blog/web-development-tips</guid>
      <description><![CDATA[Share 5 key best practices in modern web development to help developers build higher quality applications.]]></description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In the rapidly evolving field of web development, mastering best practices is crucial for building high-quality, maintainable applications. This article shares 5 key modern web development best practices.</p>
<h2 id="1-component-based-development">1. Component-Based Development</h2>
<p>Componentization is the core concept of modern frontend development. By breaking down UI into independent, reusable components, we can:</p>
<ul>
<li>Improve code reusability</li>
<li>Simplify maintenance work</li>
<li>Enhance team collaboration efficiency</li>
</ul>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Example: Reusable button component</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">ButtonProps</span> {
  <span class="hljs-attr">variant</span>: <span class="hljs-string">'primary'</span> | <span class="hljs-string">'secondary'</span>
  <span class="hljs-attr">size</span>: <span class="hljs-string">'sm'</span> | <span class="hljs-string">'md'</span> | <span class="hljs-string">'lg'</span>
  <span class="hljs-attr">children</span>: <span class="hljs-title class_">React</span>.<span class="hljs-property">ReactNode</span>
  <span class="hljs-attr">onClick</span>?: <span class="hljs-function">() =></span> <span class="hljs-built_in">void</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Button</span>(<span class="hljs-params">{ variant, size, children, onClick }: <span class="hljs-title class_">ButtonProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">button</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">btn</span> <span class="hljs-attr">btn-</span>${<span class="hljs-attr">variant</span>} <span class="hljs-attr">btn-</span>${<span class="hljs-attr">size</span>}`}
      <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onClick}</span>
    ></span>
      {children}
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span></span>
  )
}
</code></pre>
<h2 id="2-performance-optimization">2. Performance Optimization</h2>
<p>Performance is a key factor in user experience. Here are some important performance optimization strategies:</p>
<h3 id="code-splitting">Code Splitting</h3>
<p>Use dynamic imports to split code and reduce initial loading time:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { lazy } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> <span class="hljs-title class_">LazyComponent</span> = <span class="hljs-title function_">lazy</span>(<span class="hljs-function">() =></span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./LazyComponent'</span>))

<span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span>></span>
      <span class="hljs-tag">&#x3C;<span class="hljs-name">LazyComponent</span> /></span>
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
  )
}
</code></pre>
<h3 id="image-optimization">Image Optimization</h3>
<p>Use Next.js Image component to automatically optimize images:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Image</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>

&#x3C;<span class="hljs-title class_">Image</span>
  src=<span class="hljs-string">"/example.jpg"</span>
  alt=<span class="hljs-string">"Example image"</span>
  width={<span class="hljs-number">800</span>}
  height={<span class="hljs-number">400</span>}
  priority={<span class="hljs-literal">false</span>}
/>
</code></pre>
<h2 id="3-type-safety">3. Type Safety</h2>
<p>Using TypeScript can greatly improve code quality and development experience:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">User</span> {
  <span class="hljs-attr">id</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">email</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">role</span>: <span class="hljs-string">'admin'</span> | <span class="hljs-string">'user'</span>
}

<span class="hljs-keyword">function</span> <span class="hljs-title function_">getUserDisplayName</span>(<span class="hljs-params"><span class="hljs-attr">user</span>: <span class="hljs-title class_">User</span></span>): <span class="hljs-built_in">string</span> {
  <span class="hljs-keyword">return</span> user.<span class="hljs-property">name</span>
}
</code></pre>
<h2 id="4-test-driven-development">4. Test-Driven Development</h2>
<p>Writing tests is an important means of ensuring code quality:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Button</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button'</span>

<span class="hljs-title function_">describe</span>(<span class="hljs-string">'Button'</span>, <span class="hljs-function">() =></span> {
  <span class="hljs-title function_">it</span>(<span class="hljs-string">'renders with correct text'</span>, <span class="hljs-function">() =></span> {
    <span class="hljs-title function_">render</span>(<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"md"</span>></span>Click me<span class="hljs-tag">&#x3C;/<span class="hljs-name">Button</span>></span></span>)
    <span class="hljs-title function_">expect</span>(screen.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">'Click me'</span>)).<span class="hljs-title function_">toBeInTheDocument</span>()
  })
})
</code></pre>
<h2 id="5-responsive-design">5. Responsive Design</h2>
<p>Ensure your application displays well on various devices:</p>
<pre><code class="hljs language-css"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">1200px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">768px</span>) {
  <span class="hljs-selector-class">.container</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">2rem</span>;
  }
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Mastering these best practices will help you build more professional, high-quality web applications. Remember, the most important thing is to choose appropriate practices based on project needs and continuously learn and improve.</p>
<hr>
<p><strong>Related Resources:</strong></p>
<ul>
<li><a href="https://nextjs.org/docs">Next.js Documentation</a></li>
<li><a href="https://www.typescriptlang.org/docs/">TypeScript Handbook</a></li>
<li><a href="https://developer.mozilla.org/">MDN Web Docs</a></li>
</ul>
<p><strong>Tags:</strong> #web-development #best-practices #performance #code-quality</p>]]></content:encoded>
      <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
      <category>web-development</category>
      <category>best-practices</category>
      <category>performance</category>
      <category>code-quality</category>
    </item>
  </channel>
</rss>