<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Hedon Log</title>
    <link>https://hedon954.github.io</link>
    <atom:link href="https://hedon954.github.io/rss.xml" rel="self" type="application/rss+xml" />
    <description>关于产品、工程、AI 学习与独立写作的长期个人博客。</description>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 25 Jul 2026 13:53:33 GMT</lastBuildDate>
    <item>
      <title>Markdown 复杂语法实验场</title>
      <link>https://hedon954.github.io/blog/markdown-lab/</link>
      <guid isPermaLink="true">https://hedon954.github.io/blog/markdown-lab/</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <description>用一篇真实文章验证 Typora 常用语法、数学公式、Mermaid、脚注、表格与原生 HTML。</description>
      <category>Markdown</category><category>Typora</category><category>Mermaid</category><category>KaTeX</category>
      <content:encoded><![CDATA[<p>这篇文章不是功能清单，而是一份可以长期保留的<strong>渲染验收样本</strong>。我会在 Typora 中常用的语法和网页输出之间建立一条可重复验证的路径。</p>
<blockquote>
<p>写作格式的价值，不在于语法多，而在于文件离开编辑器之后仍能完整表达。</p>
</blockquote>
<h2>基础排版与 GFM</h2>
<p>一段文字可以同时包含<strong>粗体</strong>、<em>斜体</em>、<del>删除线</del>、<code>inline code</code> 和 <a href="https://typora.io/">外部链接</a>。</p>
<ul>
<li>第一层列表<ul>
<li>第二层列表<ul>
<li>第三层列表</li>
</ul>
</li>
</ul>
</li>
<li>列表之后仍然保持稳定的段落节奏</li>
</ul>
<ol>
<li>写作</li>
<li>本地预览</li>
<li>发布与验证</li>
</ol>
<ul>
<li><input checked="" disabled="" type="checkbox"> 标题、引用、列表和代码</li>
<li><input checked="" disabled="" type="checkbox"> 表格、删除线与任务列表</li>
<li><input checked="" disabled="" type="checkbox"> 数学公式与 Mermaid</li>
<li><input disabled="" type="checkbox"> 用新的真实文章继续回归测试</li>
</ul>
<table>
<thead>
<tr>
<th>能力</th>
<th align="center">写法</th>
<th align="right">网页表现</th>
</tr>
</thead>
<tbody><tr>
<td>GFM</td>
<td align="center"><code>remark-gfm</code></td>
<td align="right">已验证</td>
</tr>
<tr>
<td>数学公式</td>
<td align="center"><code>$...$</code> / <code>$$...$$</code></td>
<td align="right">KaTeX</td>
</tr>
<tr>
<td>图表</td>
<td align="center">Mermaid fenced code</td>
<td align="right">SVG</td>
</tr>
</tbody></table>
<h2>数学公式</h2>
<p>行内公式会和文字一起流动，例如注意力的缩放项 $\frac{QK^\top}{\sqrt{d_k}}$。</p>
<p>块级公式支持多行对齐：</p>
<p>$$
\begin{aligned}
\operatorname{Attention}(Q,K,V)
  &amp;= \operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \
K_{\text{cache}}^{(t)}
  &amp;= [K_{\text{cache}}^{(t-1)}; K_t] \
V_{\text{cache}}^{(t)}
  &amp;= [V_{\text{cache}}^{(t-1)}; V_t]
\end{aligned}
$$</p>
<p>矩阵也能正常显示：</p>
<p>$$
X =
\begin{bmatrix}
1 &amp; 0 &amp; 1 \
0 &amp; 1 &amp; 0 \
1 &amp; 1 &amp; 1
\end{bmatrix}
$$</p>
<h2>Mermaid 流程与时序</h2>
<p>所有 Mermaid 图表固定使用「中性」主题。每张图都可以在「图表」与「Code」之间切换：前者用于阅读，后者用于检查或复制原始 Mermaid 语法。</p>
<p>下面的流程图描述本地写作到发布的闭环：</p>
<pre><code class="language-mermaid">flowchart LR
  A[Typora 写作] --&gt; B{本地验证}
  B --&gt;|通过| C[生成内容索引]
  B --&gt;|失败| A
  C --&gt; D[构建网站]
  D --&gt; E[浏览器验收]
  E --&gt; F[发布]
</code></pre>
<p>同一篇文章里可以继续放更复杂的时序图：</p>
<pre><code class="language-mermaid">sequenceDiagram
  autonumber
  participant W as Writer
  participant M as Markdown
  participant S as Site
  participant R as Reader
  W-&gt;&gt;M: 保存 .md 与媒体
  M-&gt;&gt;S: 构建内容与目录
  S--&gt;&gt;W: 返回预览
  W-&gt;&gt;S: 发布已验证版本
  R-&gt;&gt;S: 阅读文章
  S--&gt;&gt;R: HTML + KaTeX + Mermaid
</code></pre>
<h2>多语言代码高亮</h2>
<p>同一种内容结构，在不同语言里应该保留各自可辨识的关键字、类型、字符串、注释与数字层级。</p>
<h3>TypeScript</h3>
<pre><code class="language-typescript">type Post&lt;TMeta extends Record&lt;string, unknown&gt;&gt; = {
  slug: string;
  meta: TMeta;
};

async function publish&lt;TMeta extends Record&lt;string, unknown&gt;&gt;(
  post: Post&lt;TMeta&gt;,
): Promise&lt;string&gt; {
  const response = await fetch(`/api/posts/${post.slug}`, {
    method: &quot;POST&quot;,
    body: JSON.stringify(post.meta),
  });
  return response.ok ? &quot;published&quot; : &quot;failed&quot;;
}
</code></pre>
<h3>Python</h3>
<pre><code class="language-python">from dataclasses import dataclass
from pathlib import Path

@dataclass(frozen=True)
class Article:
    title: str
    source: Path

def published_titles(articles: list[Article]) -&gt; list[str]:
    return [article.title for article in articles if article.source.exists()]
</code></pre>
<h3>Rust</h3>
<pre><code class="language-rust">#[derive(Debug)]
struct Entry&lt;&#39;a&gt; {
    title: &amp;&#39;a str,
    pinned: bool,
}

fn pinned_titles(entries: &amp;[Entry&lt;&#39;_&gt;]) -&gt; Vec&lt;&amp;str&gt; {
    entries.iter()
        .filter(|entry| entry.pinned)
        .map(|entry| entry.title)
        .collect()
}
</code></pre>
<h3>Go</h3>
<pre><code class="language-go">func renderAll(ctx context.Context, slugs []string) &lt;-chan Result {
	results := make(chan Result)
	go func() {
		defer close(results)
		for _, slug := range slugs {
			results &lt;- render(ctx, slug)
		}
	}()
	return results
}
</code></pre>
<h3>SQL</h3>
<pre><code class="language-sql">WITH ranked_posts AS (
  SELECT
    title,
    topic,
    ROW_NUMBER() OVER (PARTITION BY topic ORDER BY published_at DESC) AS rank
  FROM posts
  WHERE status = &#39;published&#39;
)
SELECT title, topic
FROM ranked_posts
WHERE rank &lt;= 3;
</code></pre>
<h3>Swift</h3>
<pre><code class="language-swift">actor ReadingProgress {
    private var offsets: [URL: Double] = [:]

    func save(_ offset: Double, for article: URL) {
        offsets[article] = max(0, offset)
    }

    func restore(for article: URL) -&gt; Double {
        offsets[article, default: 0]
    }
}
</code></pre>
<h2>Diff、图片与原生 HTML</h2>
<pre><code class="language-diff">- const publishing = &quot;copy and paste&quot;;
+ const publishing = &quot;content as source&quot;;
</code></pre>
<p><img src="https://images.unsplash.com/photo-1501525771695-688643efeea4?auto=format&fit=crop&w=1400&q=82" alt="木桌上的笔记本与钢笔"></p>
<details>
  <summary>展开原生 HTML 内容</summary>
  <p>Typora 文件中的 details / summary 会保留为可交互的折叠区域。</p>
</details>

<hr>
<p>最后用一个脚注说明这篇文章的角色：它既是示例，也是未来升级 Markdown 依赖时的回归测试。[^regression]</p>
<p>[^regression]: 页面构建测试会检查公式与示例内容，浏览器验收会检查 Mermaid 是否被转换为 SVG。</p>
]]></content:encoded>
    </item>
    <item>
      <title>雨后的城市</title>
      <link>https://hedon954.github.io/thoughts/rainy-night/</link>
      <guid isPermaLink="true">https://hedon954.github.io/thoughts/rainy-night/</guid>
      <pubDate>Fri, 24 Jul 2026 14:36:00 GMT</pubDate>
      <description>下过雨的路面会把城市复制一遍。上面那个版本负责赶路，下面那个版本只负责发光。 这一条随想故意把三张照片、一段声音、一段视频和一个链接放在一起，用来确认日常记录不需要被单一媒体类型限制。</description>
      <category>摄影</category>
      <content:encoded><![CDATA[<p>下过雨的路面会把城市复制一遍。上面那个版本负责赶路，下面那个版本只负责发光。</p>
<p>这一条随想故意把三张照片、一段声音、一段视频和一个链接放在一起，用来确认日常记录不需要被单一媒体类型限制。</p>
]]></content:encoded>
    </item>
    <item>
      <title>今天的声音</title>
      <link>https://hedon954.github.io/thoughts/field-recording/</link>
      <guid isPermaLink="true">https://hedon954.github.io/thoughts/field-recording/</guid>
      <pubDate>Wed, 22 Jul 2026 10:10:00 GMT</pubDate>
      <description>写代码时循环播放的一段声音。相比歌单，我最近更喜欢没有明确歌词的背景，它不会替思考决定方向。</description>
      <category>声音</category>
      <content:encoded><![CDATA[<p>写代码时循环播放的一段声音。相比歌单，我最近更喜欢没有明确歌词的背景，它不会替思考决定方向。</p>
]]></content:encoded>
    </item>
    <item>
      <title>注意力也是一种界面</title>
      <link>https://hedon954.github.io/blog/attention-is-an-interface/</link>
      <guid isPermaLink="true">https://hedon954.github.io/blog/attention-is-an-interface/</guid>
      <pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
      <description>当工具越来越聪明，真正稀缺的不是功能，而是我们把注意力放在哪里。</description>
      <category>产品设计</category><category>注意力</category>
      <content:encoded><![CDATA[<p>工具总是在争取更短的路径：少一次点击、少一个页面、少等一秒。但如果只把效率当作终点，我们很容易做出一种<strong>没有摩擦，也没有方向</strong>的界面。</p>
<blockquote>
<p>好的界面不只是让事情更快发生，也应该帮助人知道什么值得发生。</p>
</blockquote>
<h2>界面在分配什么</h2>
<p>每一个高亮、通知和默认选项，都在替用户分配注意力。它们看上去只是视觉决策，实际上却在参与判断。</p>
<table>
<thead>
<tr>
<th>界面动作</th>
<th>隐含的决定</th>
</tr>
</thead>
<tbody><tr>
<td>默认展开</td>
<td>这件事更值得先看</td>
</tr>
<tr>
<td>红色角标</td>
<td>这件事不能等</td>
</tr>
<tr>
<td>无限滚动</td>
<td>这里没有自然的终点</td>
</tr>
</tbody></table>
<h2>摩擦不是敌人</h2>
<p>有些摩擦应该被删掉，例如重复输入和无意义的确认；另一些摩擦则应该被保留，例如发布前的停顿、删除前的复核，以及阅读长文时清晰的段落边界。</p>
<pre><code class="language-ts">type Friction = {
  cost: number;
  protectsIntent: boolean;
};

const shouldRemove = (friction: Friction) =&gt;
  friction.cost &gt; 0 &amp;&amp; !friction.protectsIntent;
</code></pre>
<h2>把节奏设计进去</h2>
<p>我现在更愿意先问三个问题：</p>
<ol>
<li>用户来到这里时，最需要看见什么？</li>
<li>哪些信息可以晚一点出现？</li>
<li>这个流程应该在哪里自然结束？</li>
</ol>
<p>这些问题没有让界面变得更复杂，反而让许多元素失去了存在的理由。</p>
<h2>最后的判断</h2>
<p>注意力不是取之不尽的资源。产品真正的克制，不是少用一种颜色，而是少向用户索取一次不必要的注意。</p>
<p>脚注也应该像在 Typora 中一样自然工作。<a href="%E8%BF%99%E4%B9%9F%E6%98%AF%E6%9C%AC%E7%AB%99%E9%80%89%E6%8B%A9%E5%AE%89%E9%9D%99%E6%8E%92%E7%89%88%E3%80%81%E6%9C%89%E9%99%90%E5%BC%BA%E8%B0%83%E8%89%B2%E5%92%8C%E6%98%8E%E7%A1%AE%E9%98%85%E8%AF%BB%E8%BE%B9%E7%95%8C%E7%9A%84%E5%8E%9F%E5%9B%A0%E3%80%82">^attention</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>慢一点看</title>
      <link>https://hedon954.github.io/thoughts/slow-motion/</link>
      <guid isPermaLink="true">https://hedon954.github.io/thoughts/slow-motion/</guid>
      <pubDate>Sun, 19 Jul 2026 01:20:00 GMT</pubDate>
      <description>很多细节不是不存在，只是平常的速度太快了。</description>
      <category>影像</category>
      <content:encoded><![CDATA[<p>很多细节不是不存在，只是平常的速度太快了。</p>
]]></content:encoded>
    </item>
    <item>
      <title>03 · 带着证据发布</title>
      <link>https://hedon954.github.io/columns/building-in-public/ship-with-evidence/</link>
      <guid isPermaLink="true">https://hedon954.github.io/columns/building-in-public/ship-with-evidence/</guid>
      <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
      <description>“我改完了”不是完成，“用户可以确认它好了”才是。</description>
      <category>验证</category><category>发布</category>
      <content:encoded><![CDATA[<p>发布前最重要的工作不是再写一段漂亮总结，而是找到足以支持结论的证据。</p>
<h2>验证真实路径</h2>
<p>测试最小单元很重要，但用户真正经历的是一条完整路径。至少有一次验证应该从入口走到结果。</p>
<h2>不隐藏边界</h2>
<p>没有验证的部分要明确说出来。诚实的边界比虚假的完整更有价值。</p>
<h2>留下下一步</h2>
<p>一次发布应该让下一次开发更容易开始，而不是重新理解所有上下文。</p>
]]></content:encoded>
    </item>
    <item>
      <title>链接不是收藏，是路径</title>
      <link>https://hedon954.github.io/thoughts/links-are-paths/</link>
      <guid isPermaLink="true">https://hedon954.github.io/thoughts/links-are-paths/</guid>
      <pubDate>Thu, 16 Jul 2026 04:05:00 GMT</pubDate>
      <description>想把最近读到的好东西放回来。收藏夹很容易变成仓库，链接出现在上下文里，才重新变成一条路。</description>
      <category>阅读</category>
      <content:encoded><![CDATA[<p>想把最近读到的好东西放回来。收藏夹很容易变成仓库，链接出现在上下文里，才重新变成一条路。</p>
]]></content:encoded>
    </item>
    <item>
      <title>我为什么重新选择本地写作</title>
      <link>https://hedon954.github.io/blog/local-first-writing/</link>
      <guid isPermaLink="true">https://hedon954.github.io/blog/local-first-writing/</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate>
      <description>Markdown 文件、可迁移的媒体资源，以及不被平台锁住的长期写作。</description>
      <category>Markdown</category><category>Local-first</category>
      <content:encoded><![CDATA[<p>我仍然在 Typora 里写下第一版。不是因为它有最多的功能，而是因为一个普通的 Markdown 文件让我知道：十年后，我仍然能打开这些文字。</p>
<h2>文件就是作品</h2>
<p>文章不应该只存在于某个编辑器的数据库里。标题、发布日期和主题放进 YAML front matter，正文保持标准 Markdown，图片则和文章一起归档。</p>
<h2>发布只是一次编译</h2>
<p>写作和发布可以分离。写作阶段只关心内容；网站读取文件，生成目录、搜索索引和 RSS。</p>
<ul>
<li><input checked="" disabled="" type="checkbox"> 在 Typora 中完成正文</li>
<li><input checked="" disabled="" type="checkbox"> 提交 Markdown 与媒体资源</li>
<li><input disabled="" type="checkbox"> 发布后继续修正</li>
</ul>
<h2>可迁移才有长期主义</h2>
<p>真正的长期主义不是选中一个“永远不会倒闭”的平台，而是让迁移成本始终足够低。</p>
]]></content:encoded>
    </item>
    <item>
      <title>02 · 让状态变得可靠</title>
      <link>https://hedon954.github.io/columns/building-in-public/stable-state/</link>
      <guid isPermaLink="true">https://hedon954.github.io/columns/building-in-public/stable-state/</guid>
      <pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate>
      <description>状态设计的关键，是明确谁拥有事实。</description>
      <category>状态管理</category>
      <content:encoded><![CDATA[<p>状态问题很少只是“少刷新了一次”。更常见的原因是多个地方都以为自己拥有最终事实。</p>
<h2>唯一事实来源</h2>
<p>先确定数据由谁拥有，再决定界面如何派生。临时交互状态可以留在视图，持久事实必须有明确边界。</p>
<h2>乐观更新</h2>
<p>乐观更新让操作显得即时，但失败时必须能够回滚，并且不能让选择状态丢失。</p>
<h2>可恢复</h2>
<p>真正可靠的状态不是永不失败，而是在失败以后仍然知道应该回到哪里。</p>
]]></content:encoded>
    </item>
    <item>
      <title>01 · 从最小闭环开始</title>
      <link>https://hedon954.github.io/columns/building-in-public/smallest-closed-loop/</link>
      <guid isPermaLink="true">https://hedon954.github.io/columns/building-in-public/smallest-closed-loop/</guid>
      <pubDate>Sat, 04 Jul 2026 00:00:00 GMT</pubDate>
      <description>先让最重要的一条路径真正跑通。</description>
      <category>产品</category><category>工程</category>
      <content:encoded><![CDATA[<p>一个人的时间有限，所以“完整”必须被重新定义。对我来说，完整不是一次做完所有功能，而是先让最重要的一条路径首尾相接。</p>
<h2>什么是最小闭环</h2>
<p>它必须包含触发、完成和反馈。只有按钮不算闭环，只有数据结构也不算。</p>
<h2>把想法切成动作</h2>
<p>以一个文件浏览器为例，第一步不是标签、搜索或批量操作，而是：</p>
<ol>
<li>创建文件夹；</li>
<li>创建 Markdown 文件；</li>
<li>刷新列表；</li>
<li>自动选中新项目。</li>
</ol>
<h2>可观察的完成</h2>
<p>闭环需要能被人看见，也能被测试验证。每次完成之后，都应该留下足够明确的证据。</p>
<h2>下一步</h2>
<p>当闭环成立，再进入命名、删除和上下文菜单。功能扩展不再是猜测，而是沿着已经成立的路径自然生长。</p>
]]></content:encoded>
    </item>
    <item>
      <title>做一个 PDF 阅读器之后，我重新理解了「位置」</title>
      <link>https://hedon954.github.io/blog/building-a-pdf-reader/</link>
      <guid isPermaLink="true">https://hedon954.github.io/blog/building-a-pdf-reader/</guid>
      <pubDate>Sun, 28 Jun 2026 00:00:00 GMT</pubDate>
      <description>阅读位置不是页码，而是一段连续体验的锚点。</description>
      <category>SwiftUI</category><category>阅读体验</category>
      <content:encoded><![CDATA[<p>“打开侧栏不能让正文跳一下”看起来是一条微小的要求，真正实现时却牵动了布局、缩放、滚动和状态恢复。</p>
<h2>页码不等于位置</h2>
<p>同一页里也有阅读进度。只记录页码，恢复后仍然可能让人失去上下文。</p>
<h2>变化前先保存锚点</h2>
<p>布局变化之前，保存视口中心所对应的文档坐标；布局完成以后，再把这个坐标映射回新的视口。</p>
<h2>两条交互路径</h2>
<p>侧栏开关是离散变化，分隔线拖拽是连续变化。它们应该共享“保持锚点”的原则，但不应该共享完全相同的更新频率。</p>
]]></content:encoded>
    </item>
    <item>
      <title>从逐词生成理解 KV Cache</title>
      <link>https://hedon954.github.io/blog/understand-kv-cache/</link>
      <guid isPermaLink="true">https://hedon954.github.io/blog/understand-kv-cache/</guid>
      <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
      <description>不从公式开始，而是从模型下一次到底重复算了什么开始。</description>
      <category>Transformer</category><category>推理</category>
      <content:encoded><![CDATA[<p>理解 KV Cache 最直观的方式，是先看自回归生成里那些被重复计算的部分。</p>
<h2>Prefill 与 Decode</h2>
<p>Prefill 一次处理完整提示词，Decode 则每次只生成一个新 token。</p>
<pre><code class="language-mermaid">flowchart LR
  A[Prompt] --&gt; B[Prefill]
  B --&gt; C[KV Cache]
  C --&gt; D[Decode one token]
  D --&gt; C
</code></pre>
<h2>缓存的是什么</h2>
<p>过去 token 对应的 Key 和 Value 不会因为新 token 到来而变化，所以可以保存下来，只计算新位置的投影。</p>
<h2>空间换时间</h2>
<p>缓存减少计算，却增加显存占用。批量、序列长度与层数共同决定了这笔开销。</p>
<p>$$
\text{KV memory} \propto 2 \times L \times H \times T
$$</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
