<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>XiaoMo · Blog</title><description>XiaoMo&apos;s personal site: writing, small games, and browser tools.</description><link>https://www.moiu.cn/</link><language>en</language><item><title>Putting a Three.js island inside a static site</title><link>https://www.moiu.cn/en/blog/aether-isle/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/aether-isle/</guid><description>The isle page downloads only its own script. The scene is computed at runtime, Three.js stays in that page&apos;s chunk, and the first HTML response carries an empty stage.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>meta</category><category>Astro</category><category>performance</category><category>design</category></item><item><title>Morse code: one table, two ambiguities, three details</title><link>https://www.moiu.cn/en/blog/morse-code/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/morse-code/</guid><description>Morse itself is trivial; the trouble is in the details — who owns the space, how to normalise full-width dots, and how long one unit actually is.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>tools</category><category>algorithms</category></item><item><title>An opponent that predicts the bounce, but not too well</title><link>https://www.moiu.cn/en/blog/pong-ai/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/pong-ai/</guid><description>A Pong opponent worth playing needs two things: an accurate prediction of where the ball lands, and a deliberate error. The first is one triangle wave; the second is redrawn on every return.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>games</category><category>algorithms</category></item><item><title>Volume without a modelling tool: triangulating an island by hand</title><link>https://www.moiu.cn/en/blog/procedural-island/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/procedural-island/</guid><description>The rock on this island is not exported from a modelling tool. It is five irregular rings stitched together at runtime, and vertex colours plus flat shading are enough to make it read as stone.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>meta</category><category>design</category></item><item><title>Twelve hundred instances: squeezing a garden into a few draw calls</title><link>https://www.moiu.cn/en/blog/three-instancing/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/three-instancing/</guid><description>Six hundred blades of grass, a hundred and twenty flowers, eighty-five mossy stones, forty-two shrubs. Density comes from InstancedMesh, not from cutting the count.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>performance</category><category>meta</category></item><item><title>Lighting one scene: three lamps and a shadow map computed once</title><link>https://www.moiu.cn/en/blog/three-lighting/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/three-lighting/</guid><description>A hemisphere light for ambience, a key light for direction and shadow, a rim light to pull the island off the background — and seven point lights that only add glow.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>design</category><category>meta</category></item><item><title>Water from two shaders: a pool, a stream and a waterfall</title><link>https://www.moiu.cn/en/blog/three-water-shader/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/three-water-shader/</guid><description>There is no water simulation on the island and no reflections. Two ShaderMaterials cover all of it, one lying flat and one falling, sharing a single time and night uniform.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>meta</category><category>design</category></item><item><title>The loading budget for 3D: split the chunk, load on visibility, know when to stop</title><link>https://www.moiu.cn/en/blog/webgl-loading-budget/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/webgl-loading-budget/</guid><description>A 3D page pays three costs — download, first-frame compilation, per-frame drawing. Each needs its own budget, and any one of them can turn good-looking into janky.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><category>performance</category><category>meta</category><category>Astro</category></item><item><title>Atomic file writes: write a temp file, then rename</title><link>https://www.moiu.cn/en/blog/atomic-write/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/atomic-write/</guid><description>Overwriting in place leaves half a file behind on power loss, a crash, or a concurrent read. A temp file plus rename uses filesystem atomicity to make half a file impossible.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>tools</category></item><item><title>Base64: it is neither encryption nor compression</title><link>https://www.moiu.cn/en/blog/base64/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/base64/</guid><description>Base64 turns 3 bytes into 4 printable characters, growing the payload by a third. It solves carrying binary through a text channel, not keeping anything secret.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>security</category></item><item><title>Misusing big O: constants and preconditions decide more than the order</title><link>https://www.moiu.cn/en/blog/big-o-misconception/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/big-o-misconception/</guid><description>An O(n log n) sort can lose to O(n²) insertion sort, and an O(1) hash lookup can lose to an array scan. Check input size, constants and memory locality before judging speed.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>performance</category></item><item><title>Using @layer to make CSS precedence a declared order, not a specificity fight</title><link>https://www.moiu.cn/en/blog/css-cascade-layers/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/css-cascade-layers/</guid><description>Runaway styles come from everyone solving problems by adding specificity. Cascade layers make the order explicit: between layers only order matters, and specificity applies within a layer.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category></item><item><title>Debounce vs throttle: the difference is who decides when to run</title><link>https://www.moiu.cn/en/blog/debounce-throttle/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/debounce-throttle/</guid><description>Both limit how often a function runs. Debounce lets the last call decide the moment; throttle uses a fixed window. The wrong pick shows up as a laggy drag or a request every 200ms.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>performance</category></item><item><title>Estimating work: why &quot;about three days&quot; becomes three weeks</title><link>https://www.moiu.cn/en/blog/developer-estimation/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/developer-estimation/</guid><description>You estimated writing code; what you owe is writing plus integration, review, rework, and waiting on others. Listing the unknowns beats multiplying every day count by two.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>backend</category></item><item><title>Feature flags are debt, not configuration</title><link>https://www.moiu.cn/en/blog/feature-flags/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/feature-flags/</guid><description>Every flag doubles the reachable code paths and no flag ever gets deleted by itself. Giving each one a removal date matters far more than building a clever flag system.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>tools</category></item><item><title>The game loop: why delta time is not optional</title><link>https://www.moiu.cn/en/blog/game-loop/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/game-loop/</guid><description>Moving 5 pixels per frame runs twice as fast on a 60Hz screen and two and a half times as fast at 144Hz. Multiply by elapsed time to decouple physics from refresh rate, then cap it.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>games</category><category>performance</category></item><item><title>git bisect: turn which commit broke it into one command</title><link>https://www.moiu.cn/en/blog/git-bisect/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/git-bisect/</guid><description>The hard part of regression hunting is not the binary search, it is defining what counts as broken. Write a script that answers that, and bisect compresses n builds into log n.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>algorithms</category></item><item><title>Gomoku AI: splitting threat detection from search</title><link>https://www.moiu.cn/en/blog/gomoku-search-engine/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/gomoku-search-engine/</guid><description>Plain alpha-beta misses forced wins in gomoku. I split the decision into three layers — win, block, forced-win patterns — and only then hand the rest to iterative deepening.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Graceful shutdown: the exit sequence decides between 502s and clean responses</title><link>https://www.moiu.cn/en/blog/graceful-shutdown/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/graceful-shutdown/</guid><description>Exiting immediately on a termination signal cuts in-flight requests. The order is stop accepting, drain what is running, release resources, then exit.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>meta</category></item><item><title>HTTP caching: Cache-Control and ETag each own half</title><link>https://www.moiu.cn/en/blog/http-caching/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/http-caching/</guid><description>Cache-Control decides whether a request happens; ETag decides whether the response body can be skipped. Mixing them is why cache bugs persist: changing one never fixes the other half.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>performance</category></item><item><title>Idempotency keys: the one write that makes retries safe</title><link>https://www.moiu.cn/en/blog/idempotency/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/idempotency/</guid><description>A network timeout does not mean the server did nothing. An idempotency key identifies the same request so the server replays the first result instead of executing again.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>security</category></item><item><title>JSON canonicalization: why the same object hashes two ways</title><link>https://www.moiu.cn/en/blog/json-canonicalization/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/json-canonicalization/</guid><description>Key order, number formatting and escape style all change the bytes. A stable hash has to pin those rules at serialization, not hope JSON.stringify is deterministic.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>security</category></item><item><title>Memory layout: the same algorithm, ten times slower, is usually cache</title><link>https://www.moiu.cn/en/blog/memory-layout/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/memory-layout/</guid><description>A trip to main memory costs hundreds of cycles; a cache line fetch costs one. Sequential versus random access often outweighs the algorithmic order itself.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>performance</category><category>algorithms</category></item><item><title>Deadlock: break any one of four conditions</title><link>https://www.moiu.cn/en/blog/mutex-deadlock/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/mutex-deadlock/</guid><description>Mutual exclusion, hold and wait, no preemption, circular wait. All four must hold at once. The practical one to break is circular wait: give every lock a global order.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>algorithms</category></item><item><title>The N+1 query: the most typical ORM performance trap</title><link>https://www.moiu.cn/en/blog/n-plus-one/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/n-plus-one/</guid><description>Fetch 100 rows and query the relation for each one and you have made 101 queries. There are only three fixes: eager load, batch fetch, or use a single join.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>performance</category></item><item><title>Rate limiting: what token bucket and leaky bucket really differ on</title><link>https://www.moiu.cn/en/blog/rate-limiting/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/rate-limiting/</guid><description>A token bucket permits bursts; a leaky bucket flattens traffic to a constant rate. Picking wrong shows up as throttled normal users, or a limit that does nothing.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>performance</category></item><item><title>Catastrophic backtracking: why a regex can pin a CPU</title><link>https://www.moiu.cn/en/blog/regex-catastrophic/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/regex-catastrophic/</guid><description>Nested quantifiers make the number of match attempts grow exponentially; (a+)+b can hang a process on one long non-matching input. Drop the nesting or put a timeout on it.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>algorithms</category></item><item><title>iframe sandbox: it narrows capability, it does not harden</title><link>https://www.moiu.cn/en/blog/sandbox-iframe/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/sandbox-iframe/</guid><description>The sandbox attribute starts by removing everything and allow-* adds pieces back. Getting the direction backwards is common: scripts stop running because that is the default.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>meta</category></item><item><title>Semantic versioning: a major bump promises no silent behavior change</title><link>https://www.moiu.cn/en/blog/semver/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/semver/</guid><description>MAJOR does not mean big. It means breaking. Using it for refactors and hiding breaks in minor releases turns your downstream automatic upgrades into outages.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>meta</category></item><item><title>SQLite indexes: the leftmost prefix is a consequence of sorting</title><link>https://www.moiu.cn/en/blog/sqlite-index/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/sqlite-index/</guid><description>A composite index (a, b) can only order by b within equal a, so skipping a and filtering on b has nothing to use. See it as sorting and the mnemonic stops being a rule to memorize.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>algorithms</category></item><item><title>What technical debt interest actually looks like</title><link>https://www.moiu.cn/en/blog/tech-debt-interest/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tech-debt-interest/</guid><description>Debt is not ugly code; it is the extra cost every future change pays. The test is measurable: files touched per edit, test runtime, places that must change together.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>tools</category></item><item><title>Time zones and DST: store UTC, or daylight saving will bite</title><link>https://www.moiu.cn/en/blog/timezone-dst/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/timezone-dst/</guid><description>A DST day has 23 or 25 hours, and local times near the switch may not exist or may happen twice. Storing UTC timestamps and converting at display is the only model that holds.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>backend</category><category>algorithms</category></item><item><title>Turning an AI search into a tree you can page through</title><link>https://www.moiu.cn/en/blog/tool-tree/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-tree/</guid><description>Visualising a search is not mainly about drawing the tree. The data has to be real, the panel must not shove the board around, and it must load only for people who click the button.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>Vue</category><category>games</category></item><item><title>Unicode normalization: why two identical-looking strings differ</title><link>https://www.moiu.cn/en/blog/unicode-normalization/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/unicode-normalization/</guid><description>An e-acute can be one code point or a base letter plus a combining mark. Normalize to NFC before comparing, storing or indexing, or duplicates quietly slip through.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>backend</category></item><item><title>URL encoding: spaces, plus signs and double encoding</title><link>https://www.moiu.cn/en/blog/url-encoding/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/url-encoding/</guid><description>A plus is a space in the query string and a literal plus in the path; the two positions encode the same character differently; encoding twice yields a plausible value that never matches.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>security</category></item><item><title>Four places Chinese chess rules go wrong</title><link>https://www.moiu.cn/en/blog/xiangqi-rules/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/xiangqi-rules/</guid><description>Horse-leg blocking, elephant-eye blocking, cannon screens and the flying-general rule. Splitting move generation into a geometry layer and a legality layer removes most of the mistakes.</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Writing a cron parser: five fields, the union rule, and February 30th</title><link>https://www.moiu.cn/en/blog/cron-parser/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cron-parser/</guid><description>The syntax takes half an hour. The semantics are the work: day-of-month and day-of-week unite rather than intersect, impossible dates need a stopping condition, and 7 and 0 are both Sunday.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>algorithms</category></item><item><title>CSV is not split on commas: quoting and newlines from RFC 4180</title><link>https://www.moiu.cn/en/blog/csv-rfc4180/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/csv-rfc4180/</guid><description>The moment a field is quoted, split stops working: quotes hold delimiters and newlines, and a quote inside a field is doubled. A thirty-line state machine beats any regex.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>algorithms</category></item><item><title>Legal moves in Reversi: eight directions, one scan</title><link>https://www.moiu.cn/en/blog/reversi-legal-moves/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/reversi-legal-moves/</guid><description>A square is legal if at least one of eight directions can sandwich the opponent. Testing and flipping share a single scan, so there is no simulated move to roll back.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>A sudoku generator: digging holes, unique solutions, and the bug that froze the page</title><link>https://www.moiu.cn/en/blog/sudoku-generator/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/sudoku-generator/</guid><description>Generate a full solution, then dig cells out while checking uniqueness. I let zero mean unlimited in the solver, so an empty grid enumerated every solution and the new-game button hung.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Consistent hashing: why adding a machine does not reshuffle every key</title><link>https://www.moiu.cn/en/blog/consistent-hashing/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/consistent-hashing/</guid><description>Modulo sharding moves almost all data when the machine count changes. Put the hash space on a ring and let each key follow it clockwise to the first node, and only one arc moves. Virtual nodes are the price.</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>meta</category></item><item><title>Winning at Gomoku: checking the last stone is enough</title><link>https://www.moiu.cn/en/blog/gomoku-win-detect/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/gomoku-win-detect/</guid><description>Rescanning the whole board after every move is waste. Only the newest stone can create a line, so four directions from that point settle it in constant time.</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Sliding windows: decide what the window tracks before writing the loop</title><link>https://www.moiu.cn/en/blog/sliding-window/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/sliding-window/</guid><description>A window works when validity changes monotonically with its edges. Write down the tracked state and the shrink condition first, and the two-pointer loop amortises to O(n) on its own.</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>performance</category></item><item><title>Monotonic stacks: next greater element in O(n), not O(n squared)</title><link>https://www.moiu.cn/en/blog/monotonic-stack/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/monotonic-stack/</guid><description>Each index is pushed and popped at most once, so what looks like a nested loop is linear. Recognise the first larger or smaller neighbour shape and the same template applies.</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>performance</category></item><item><title>Counting the ones in a binary number: from loops to SWAR</title><link>https://www.moiu.cn/en/blog/popcount-bit-tricks/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/popcount-bit-tricks/</guid><description>Popcount has three forms: loop once per set bit, a lookup table, or SWAR across bit fields. They differ by orders of magnitude, and Rust and WebAssembly already expose it as a single instruction.</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>performance</category></item><item><title>Union-find: two lines of optimisation make every operation near constant</title><link>https://www.moiu.cn/en/blog/union-find/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/union-find/</guid><description>Path compression flattens the tree, union by size stops it growing tall. You need both before the cost drops to the inverse Ackermann function, which is never above 5 in practice.</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>performance</category></item><item><title>In Rust, context belongs on the line that actually fails</title><link>https://www.moiu.cn/en/blog/rust-result-context/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/rust-result-context/</guid><description>The question mark operator rethrows an error without saying which step failed. Structured errors in the core, Context in the application layer, with the input attached, is what makes a log line locatable.</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><category>Rust</category><category>backend</category></item><item><title>When a crate should become a workspace</title><link>https://www.moiu.cn/en/blog/rust-workspace-split/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/rust-workspace-split/</guid><description>The trigger for splitting a workspace is build time and dependency boundaries, not tidy directories. Split when three signals hold, or you trade one compile for managing several packages.</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><category>Rust</category><category>backend</category></item><item><title>Structured logging is not println replaced by tracing::info</title><link>https://www.moiu.cn/en/blog/tracing-structured-logs/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tracing-structured-logs/</guid><description>Log lines built by concatenation can only be read by humans. Fields make them queryable. Fields, spans, levels and knowing when not to format are the four real decisions.</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><category>Rust</category><category>backend</category></item><item><title>Islands are not Web Components: two different kinds of on-demand loading</title><link>https://www.moiu.cn/en/blog/islands-vs-webcomponents/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/islands-vs-webcomponents/</guid><description>Islands decide at build time where JavaScript is needed and when it hydrates. Web Components give the browser a self-contained element at runtime. Confusing them picks the wrong tool.</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><category>Astro</category><category>performance</category></item><item><title>Cache headers for a static site: hashed files forever, HTML never</title><link>https://www.moiu.cn/en/blog/nginx-cache-headers/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/nginx-cache-headers/</guid><description>A cache policy depends on whether the filename changes with the content, not on file size. Long-caching HTML hands users an old entry point that points at deleted chunks.</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>performance</category></item><item><title>When WebAssembly earns its place: less often than the pitch suggests</title><link>https://www.moiu.cn/en/blog/wasm-where-to-use/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/wasm-where-to-use/</guid><description>WASM pays off with heavy computation, bulk data and few boundary crossings. Rewriting an O(n) JavaScript loop as WASM usually buys slower loading and a harder stack to debug.</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><category>performance</category><category>meta</category></item><item><title>Anchors hidden behind a sticky header: one scroll-padding-top for the whole site</title><link>https://www.moiu.cn/en/blog/anchor-scroll-padding/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/anchor-scroll-padding/</guid><description>A sticky header covers every anchor target. scroll-margin per heading always misses one; scroll-padding-top on the scroll container does not, fed by the real header height.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category></item><item><title>Use the native dialog element: you get more than you think</title><link>https://www.moiu.cn/en/blog/dialog-a11y/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/dialog-a11y/</guid><description>A hand-rolled modal needs a focus trap, ESC handling, scroll locking and focus restore. Native dialog with showModal has all of it plus ::backdrop, and one style rule as the price.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category></item><item><title>Dark mode forms stay white: color-scheme is the switch</title><link>https://www.moiu.cn/en/blog/form-color-scheme/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/form-color-scheme/</guid><description>Painting a dark background does not darken native controls. Select popups, date pickers and autofill follow color-scheme; accent-color only reaches checkboxes and sliders.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category></item><item><title>Stop guessing the header height: measure it with ResizeObserver</title><link>https://www.moiu.cn/en/blog/resize-observer-layout/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/resize-observer-layout/</guid><description>A wrapping navigation, late webfonts and changing content all break constants written into CSS. A ResizeObserver watches the element and writes the measured value back into a variable.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>performance</category></item><item><title>Design tokens: one file that runs the whole site</title><link>https://www.moiu.cn/en/blog/design-tokens/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/design-tokens/</guid><description>Why every colour, spacing step and corner radius lives in _tokens.scss — and why changing this site&apos;s entire visual direction only touched two files.</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category></item><item><title>Manual i18n: what hand-rolling three languages costs</title><link>https://www.moiu.cn/en/blog/manual-i18n/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/manual-i18n/</guid><description>I hand-rolled this site&apos;s i18n instead of using Astro&apos;s built-in config. Here is why, and exactly where the cost shows up — two 404 incidents and a blog that was half translated.</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>design</category></item><item><title>2048: collapsing four directions into one operation</title><link>https://www.moiu.cn/en/blog/algo-2048/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/algo-2048/</guid><description>Sliding, merging and scoring only need to be written once; the other three directions reuse it through transpose and reverse. The hard part is that each tile may merge at most once per move.</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Minesweeper: deferring mine placement until the first click</title><link>https://www.moiu.cn/en/blog/algo-minesweeper/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/algo-minesweeper/</guid><description>A minesweeper where the first click can lose is an unfinished minesweeper. Deferring placement and excluding the surrounding 3 by 3 is the single most important design decision here.</description><pubDate>Sat, 19 Sep 2026 00:00:00 GMT</pubDate><category>algorithms</category><category>games</category></item><item><title>Turning screen-printing limits into design tokens</title><link>https://www.moiu.cn/en/blog/riso-ui/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/riso-ui/</guid><description>This site&apos;s visual direction is Riso screen printing. What I actually built was the printing constraints as tokens, so retheming touched two files and no component changed.</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><category>design</category><category>CSS</category></item><item><title>Base and unit conversion: temperature is the only non-linear one</title><link>https://www.moiu.cn/en/blog/tool-convert/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-convert/</guid><description>Converting between bases 2 and 36 needs BigInt to avoid silent precision loss. Across ten unit categories nine are linear ratios, while temperature is affine, and a single multiplier is always wrong.</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>Vue</category></item><item><title>Color and contrast: relative luminance is not an average</title><link>https://www.moiu.cn/en/blog/tool-color/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-color/</guid><description>Converting between notations is just the entry point. The real trap is WCAG contrast, which needs gamma decoding before weighting, and deciding between black and white text, which is not a brightness threshold.</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>Vue</category></item><item><title>Full-text search on a static site: index at build time, no backend at runtime</title><link>https://www.moiu.cn/en/blog/pagefind-search/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/pagefind-search/</guid><description>A static site does not need Algolia to have search. Generate the index during the build, run the query entirely in the browser, and pay a few seconds of build time.</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate><category>meta</category><category>performance</category></item><item><title>Hash and checksum: why CRC32 and MD5 have to be written by hand</title><link>https://www.moiu.cn/en/blog/tool-hash/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-hash/</guid><description>The browser only offers the SHA family, and only in a secure context. CRC32 and MD5 are not in Web Crypto. Along the way: large files should not be read into memory whole, and checksums should not be compared by eye.</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>Vue</category></item><item><title>Regex Lab: hits, groups, replacement preview, and the zero-width trap</title><link>https://www.moiu.cn/en/blog/tool-regex-lab/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-regex-lab/</guid><description>A regex tester has to do three things well: show where matches land and what each group captured, preview the replacement, and never hang on a zero-width match.</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>Vue</category></item><item><title>JSON Studio: format, validate and diff in one page</title><link>https://www.moiu.cn/en/blog/tool-json-studio/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/tool-json-studio/</guid><description>Built from the habit of writing throwaway commands to clean up JSON. It formats, minifies, sorts, pinpoints parse errors by line and column, and diffs two documents — all in the browser.</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate><category>tools</category><category>Vue</category></item><item><title>1059 assertions, no test framework: why pure logic must import nothing</title><link>https://www.moiu.cn/en/blog/zero-import-selftest/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/zero-import-selftest/</guid><description>My test suite is `node scripts/selftest.ts` — no framework, no dependencies. The price is that every module it touches must have zero relative imports, and that price bought a codebase shape.</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate><category>Astro</category><category>meta</category></item><item><title>SharePoint deserialisation RCE: CVE-2026-45659</title><link>https://www.moiu.cn/en/blog/cve-2026-45659-sharepoint/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cve-2026-45659-sharepoint/</guid><description>A plain Site Member account is enough to run code on a SharePoint server. Microsoft rated exploitation unlikely, and CISA put it in the KEV catalogue with three days to remediate.</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>vulnerability</category></item><item><title>n8n unauthenticated RCE (Ni8mare): CVE-2026-21858</title><link>https://www.moiu.cn/en/blog/cve-2026-21858-n8n/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cve-2026-21858-n8n/</guid><description>The form node skips Content-Type validation, so one application/json request makes it read an arbitrary file. The key inside the config forges an admin JWT, and a sandbox bypass turns that into RCE.</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>vulnerability</category></item><item><title>Cisco FMC unauthenticated Java deserialisation RCE: CVE-2026-20131</title><link>https://www.moiu.cn/en/blog/cve-2026-20131-cisco-fmc/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cve-2026-20131-cisco-fmc/</guid><description>CVSS 10.0, no authentication, code execution as root, and 36 days of use as a zero-day by a ransomware crew. Breaking the firewall management hub hands over every policy and log with it.</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><category>security</category><category>vulnerability</category></item><item><title>Pushing first-load JS to zero with Astro islands</title><link>https://www.moiu.cn/en/blog/astro-islands/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/astro-islands/</guid><description>From &quot;async import&quot; to &quot;hydrate on demand&quot; — what each of the three lazy-loading layers actually solves, with the code that runs on this site.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><category>Astro</category><category>performance</category><category>Vue</category></item><item><title>Next.js incremental cache path traversal to RCE: CVE-2026-75604</title><link>https://www.moiu.cn/en/blog/cve-2026-75604-nextjs/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cve-2026-75604-nextjs/</guid><description>One unescaped backslash turns the incremental cache into arbitrary file read and write on Windows, and the leaked Server Action encryption key forges a request that walks a React Flight property chain to Function.</description><pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate><category>security</category><category>vulnerability</category></item><item><title>React Router prototype pollution chained into RCE: CVE-2026-42211</title><link>https://www.moiu.cn/en/blog/cve-2026-42211-react-router/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/cve-2026-42211-react-router/</guid><description>turbo-stream v2 calls a constructor while rehydrating the TYPE_ERROR branch without checking where it came from. If the app already has a prototype pollution bug, that path reaches Function and two moderate flaws become RCE.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><category>security</category><category>vulnerability</category></item><item><title>One source for head: SiteHead, JSON-LD escaping and hreflang</title><link>https://www.moiu.cn/en/blog/head-single-source/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/head-single-source/</guid><description>All three HTML shells render one component that emits every head tag, with the data assembled on the server. Plus a detail worth remembering: angle brackets in JSON-LD must be escaped.</description><pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate><category>meta</category><category>performance</category></item><item><title>Shipping a static site is not &quot;upload the folder&quot;: caching, compression, honest 404s</title><link>https://www.moiu.cn/en/blog/static-hosting-nginx/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/static-hosting-nginx/</guid><description>Almost every static-hosting bug lives in response headers. Which files may be cached forever, which may not be cached at all, and why a static site must never use an SPA fallback.</description><pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate><category>meta</category><category>performance</category></item><item><title>A theme-switch ripple with the View Transitions API</title><link>https://www.moiu.cn/en/blog/theme-ripple/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/theme-ripple/</guid><description>A circle growing from the click point reveals the new theme. The radius must reach the farthest corner, and the custom properties must be cleaned up or they leak into the next switch.</description><pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate><category>CSS</category><category>design</category><category>Vue</category></item><item><title>Pagination at build time: why a static site should not page with a query string</title><link>https://www.moiu.cn/en/blog/build-time-pagination/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/build-time-pagination/</guid><description>The post list is sliced into static pages during the build, so page N is a real route. No client-side paging logic, no query string, just links.</description><pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate><category>Astro</category><category>performance</category></item><item><title>The @/ alias lives in three configs, and all three are required</title><link>https://www.moiu.cn/en/blog/alias-three-places/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/alias-three-places/</guid><description>Deep relative imports are banned project-wide, and the price is configuring the alias in three places. Miss one and you get editor errors, a broken build, or silently dropped styles.</description><pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate><category>Astro</category><category>meta</category></item><item><title>zod as a frontmatter contract: bad articles fail the build</title><link>https://www.moiu.cn/en/blog/zod-frontmatter/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/zod-frontmatter/</guid><description>The Content Collections schema is the single source of truth. A title over 120 characters, a mangled date or a string where an array belongs fails immediately instead of silently dropping a page.</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><category>Astro</category><category>meta</category></item><item><title>Why I built yet another personal site</title><link>https://www.moiu.cn/en/blog/hello-world/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/hello-world/</guid><description>Not because there was nowhere to write, but because there was nowhere to put writing, small games and tools side by side.</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><category>meta</category><category>Astro</category></item><item><title>A zero-config registry: adding a game means adding a directory</title><link>https://www.moiu.cn/en/blog/glob-registry/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/glob-registry/</guid><description>App lists, detail routes, the home page picks and the sitemap are all discovered by import.meta.glob. The price is Vite&apos;s hard rule: literal relative paths only.</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><category>Astro</category><category>meta</category></item><item><title>No client:only for app pages: a static shell plus an explicit mount</title><link>https://www.moiu.cn/en/blog/app-shell-mount/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/app-shell-mount/</guid><description>Game and tool pages are a shell plus createApp, not client:only. I own the loading, ready and error states, and the retry button actually works.</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><category>Vue</category><category>performance</category></item><item><title>Views hold no logic: all data assembly lives in src/server</title><link>https://www.moiu.cn/en/blog/server-layer/</link><guid isPermaLink="true">https://www.moiu.cn/en/blog/server-layer/</guid><description>A page here exports a route, imports two components and passes props. Everything else — fetching, paths, dates — happens in src/server. Here is what that buys, and the type constraint it forces.</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><category>Astro</category><category>meta</category></item></channel></rss>