Przeglądaj źródła

refactor(client): take known-site link marks from simple-icons

Review asked for a maintained artwork source instead of the fourteen
copied Simple Icons paths. SiteGlyph now imports siGithub and its
thirteen peers from the simple-icons package (CC0-1.0), added as a
ui-primitives devDependency and pinned by the lockfile, so a mark is one
host-map entry and updates arrive with the package. Each mark draws in a
28-unit viewBox inset by two units, which keeps the ~8% margin the
20-unit ic_ds_* glyphs carry, and the gallery fixture uses a synthetic
GitHub URL because verify-repository-references rejects the
organization URL.

Also from that review: the spec covers github.io and x.com, keeps its
comment truthful about which aliases collapse, and names the
single-label-host fallback case; the older clickable-link note states
the url glyph and per-site marks in the present tense; the package
README records that the known-site list is fixed and everything else
keeps the globe.
Turtle 1 tydzień temu
rodzic
commit
f018fe8f10

+ 2 - 2
.agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.md
-2026-09-04-web-clickable-link-styles.md: ea31b774f6e401c0579cfda976aecfad14bf403f
-2026-09-04-web-clickable-link-styles.zh.md: e3d8aff2ffc66cef52d4fb6b90f839badc7a0873
+2026-09-04-web-clickable-link-styles.md: fc383387cede1517981358fd31a08f47c2d76c02
+2026-09-04-web-clickable-link-styles.zh.md: 7336a740e2ba93fe67a504745dcf770cab3c803d

+ 2 - 2
.agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.md

@@ -13,7 +13,7 @@ Clickable artifact links in the chat transcript wore four different costumes: ma
 One link language across the transcript's clickable-link surfaces — markdown anchors (including reference links, mailto, and URL-promoted inline code), prose file mentions, web search source links and the fetch URL, produced-file chips, and workflow member links:
 
 - Color comes through a dedicated `--dsw-alias-link` alias in `design-platform.css` (light `deepseek-500`, dark `deepseek-400`), decoupled from `state-business-primary`; links render at `font-weight: 500` with no underline at rest and `underline dotted` at 3px offset on hover/focus.
-- A leading category glyph — the new `LinkIcon` in ui-primitives with kinds `url` (globe), `folder`, `code`, `image`, `document`, and `other` (paper) — renders `currentColor` only; `classifyLinkPath` folds the [shared detailed file-type classification](2026-09-08-shared-file-type-icons.md) into those six link categories, and code, web, and data extensions share the code glyph by design. Two anchor shapes carry no glyph: workflow member links (an in-app member view fits no file or URL category) and anchors wrapping only images (a badge or thumbnail — a dangling globe beside the picture leads no text). Inline glyphs sit at 1.1em with a −0.25em baseline offset; the flex-centered produced-file glyphs instead nudge 1.2px down because the 22px text box carries its glyphs below box center.
+- A leading category glyph — the new `LinkIcon` in ui-primitives with kinds `url`, `folder`, `code`, `image`, `document`, and `other` (paper) — renders `currentColor` only; the `url` glyph is the globe, or the destination site's own mark when its host is a known site ([known-site link marks](2026-09-16-known-site-link-marks.md)); `classifyLinkPath` folds the [shared detailed file-type classification](2026-09-08-shared-file-type-icons.md) into those six link categories, and code, web, and data extensions share the code glyph by design. Two anchor shapes carry no glyph: workflow member links (an in-app member view fits no file or URL category) and anchors wrapping only images (a badge or thumbnail — a dangling globe beside the picture leads no text). Inline glyphs sit at 1.1em with a −0.25em baseline offset; the flex-centered produced-file glyphs instead nudge 1.2px down because the 22px text box carries its glyphs below box center.
 - Produced-file chips drop the grey pill and the 96px cap: plain link-blue text at natural width that shrinks with ellipsis only when the row overflows; the container-query bands still budget 96px per chip when choosing how many chips to show.
 - Deliberately untouched: ToolRow's grey dotted file links, and the grey "Show in folder" action (it gains the folder glyph but keeps its grey style).
 - In the same pass, the inline-code chip tint moved from `neutral-bluish-100` to `neutral-50` (dark: `neutral-800`) and gained a 0.5px l1 border.
@@ -23,7 +23,7 @@ Coverage: a LinkIcon unit spec (one distinct glyph per kind, classification tabl
 ## Alternatives considered
 
 - **Colored Word/Excel/PPT/PDF link glyphs.** Rejected: fixed brand fills break the icon set's currentColor-only rule, so those extensions fold into the single outline `document` glyph. The larger file-card primitive uses distinct current-color silhouettes instead.
-- **Per-extension link icons.** Collapsed to six categories: more glyphs than the eye can parse at 14px adds noise. The 28px `FileTypeIcon` owns the more detailed file identities, and per-site marks arrived later behind the same `url` category ([known-site link marks](2026-09-16-known-site-link-marks.md)).
+- **Per-extension link icons.** Collapsed to six categories: more glyphs than the eye can parse at 14px adds noise. The 28px `FileTypeIcon` owns the more detailed file identities, and per-site marks live behind the same `url` category ([known-site link marks](2026-09-16-known-site-link-marks.md)).
 - **Keeping links on `state-business-primary`.** Darker link blues (blue-600/650/700 were auditioned and reverted) would have dragged focus rings and state dots along; the dedicated alias localizes any future tuning to one line.
 - **Glyphs on ToolRow path links.** Rejected: tool rows keep their quieter grey dotted affordance, and leading glyphs there would stack icons in already dense rows.
 

+ 2 - 2
.agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.zh.md

@@ -13,7 +13,7 @@ Status: implemented
 会话记录的可点击链接表面——Markdown 锚点(含引用式链接、mailto、被提升为链接的 inline code)、正文文件引用、网页搜索来源链接与抓取 URL、产物 chips、workflow 成员链接——统一为一套链接语言:
 
 - 颜色经由 `design-platform.css` 中专用的 `--dsw-alias-link` 别名(亮色 `deepseek-500`,暗色 `deepseek-400`),与 `state-business-primary` 解耦;链接以 `font-weight: 500` 呈现,默认无下划线,hover/focus 时为 3px offset 的 `underline dotted`。
-- 前置分类图标——ui-primitives 新增的 `LinkIcon`,kind 为 `url`(地球)、`folder`、`code`、`image`、`document`、`other`(纸张)——只渲染 `currentColor`;`classifyLinkPath` 把[共享精细文件类型分类](2026-09-08-shared-file-type-icons.zh.md)折叠进这六种链接类别,代码、网页、数据扩展名按设计共用 code 图形。两类锚点不带图标:workflow 成员链接(应用内成员视图不属于任何文件或 URL 类别)和只包图片的锚点(徽章或缩略图——图片旁悬着的地球没有可引导的文字)。行内图标为 1.1em、基线偏移 −0.25em;flex 居中的产物图标则下移 1.2px,因为 22px 文字盒的字形低于盒中心。
+- 前置分类图标——ui-primitives 新增的 `LinkIcon`,kind 为 `url`、`folder`、`code`、`image`、`document`、`other`(纸张)——只渲染 `currentColor`;`url` 图形为地球,目的地主机属于已知站点时则为该站点自己的标记([已知站点链接标记](2026-09-16-known-site-link-marks.zh.md));`classifyLinkPath` 把[共享精细文件类型分类](2026-09-08-shared-file-type-icons.zh.md)折叠进这六种链接类别,代码、网页、数据扩展名按设计共用 code 图形。两类锚点不带图标:workflow 成员链接(应用内成员视图不属于任何文件或 URL 类别)和只包图片的锚点(徽章或缩略图——图片旁悬着的地球没有可引导的文字)。行内图标为 1.1em、基线偏移 −0.25em;flex 居中的产物图标则下移 1.2px,因为 22px 文字盒的字形低于盒中心。
 - 产物 chips 去掉灰色药丸和 96px 上限:纯链接蓝文字按自然宽度展示,仅当整行溢出时才收缩出省略号;容器查询档位在决定展示几个 chip 时仍按每个 96px 预算。
 - 刻意不动:ToolRow 的灰色点线文件链接,以及灰色的「在文件夹中显示」操作(它获得文件夹图标但保持灰色样式)。
 - 同一批次中,inline code 底色从 `neutral-bluish-100` 换到 `neutral-50`(暗色:`neutral-800`),并新增 0.5px l1 描边。
@@ -23,7 +23,7 @@ Status: implemented
 ## 备选方案
 
 - **彩色 Word/Excel/PPT/PDF 链接图形。** 否决:固定品牌填充违反图标集 currentColor-only 规则,因此这些扩展名并入单一的 outline `document` 图形。较大的文件卡片 primitive 改用各自不同的 current-color 轮廓。
-- **每个扩展名一个链接图标。** 收敛为六个类别:14px 下超出肉眼可分辨数量的图形只会增加噪音。28px 的 `FileTypeIcon` 拥有更精细的文件身份;按站点标记后来在同一 `url` 类别之下引入([已知站点链接标记](2026-09-16-known-site-link-marks.zh.md))。
+- **每个扩展名一个链接图标。** 收敛为六个类别:14px 下超出肉眼可分辨数量的图形只会增加噪音。28px 的 `FileTypeIcon` 拥有更精细的文件身份;按站点标记位于同一 `url` 类别之下([已知站点链接标记](2026-09-16-known-site-link-marks.zh.md))。
 - **链接继续用 `state-business-primary`。** 更深的链接蓝(试过 blue-600/650/700 又回退)会连带焦点环和状态点;专用别名把未来的调色收敛到一行。
 - **给 ToolRow 路径链接加图形。** 否决:工具行保持更安静的灰色点线示能,在已经很密的行里加前置图形会造成图标堆叠。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-09-16-known-site-link-marks.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-known-site-link-marks.md
-2026-09-16-known-site-link-marks.md: ae7500c90bb0addf069f0917665f23211c8a3234
-2026-09-16-known-site-link-marks.zh.md: 17628fc182b5957233b727102d9efcceb055dc6d
+2026-09-16-known-site-link-marks.md: 403087beb2abd05da525a6b892935038b9ef561b
+2026-09-16-known-site-link-marks.zh.md: 4a7aeae62e43d02191cd9ecf1d22db75b553adfa

+ 9 - 5
.agents/notes/implemented/feature/2026-09-16-known-site-link-marks.md

@@ -12,22 +12,26 @@ Every transcribed anchor led with the same globe, so a transcript full of GitHub
 
 `LinkIcon` takes an optional `href`. For the `url` category, a destination whose host is a well-known site draws that site's own mark; every other `url` destination keeps the globe, and the file categories ignore `href` because their destination is a path, not a site.
 
-`SiteGlyph.tsx` in ui-primitives owns the mapping: eighteen host suffixes resolve to fourteen marks — GitHub (`github.com`, `github.io`, `raw.githubusercontent.com`), GitLab, npm, PyPI, Stack Overflow, MDN, Wikipedia, Hacker News, YouTube (`youtube.com`, `youtu.be`), X (`x.com`, `twitter.com`), Bilibili, Zhihu, Juejin, and CSDN. A host matches a suffix when it equals it or is a subdomain of it, after `www.` is stripped, so `gist.github.com` and `en.wikipedia.org` resolve without their own entries. Only absolute `http:` and `https:` destinations can match; anything else falls back to the globe.
+`SiteGlyph.tsx` in ui-primitives owns the mapping: eighteen host suffixes resolve to fourteen marks — GitHub (`github.com`, `github.io`, `raw.githubusercontent.com`), GitLab, npm, PyPI, Stack Overflow, MDN, Wikipedia, Hacker News (`news.ycombinator.com`), YouTube (`youtube.com`, `youtu.be`), X (`x.com`, `twitter.com`), Bilibili, Zhihu, Juejin, and CSDN. A host matches a suffix when it equals it or is a subdomain of it, so `gist.github.com` and `en.wikipedia.org` resolve without their own entries. Only absolute `http:` and `https:` destinations can match; anything else falls back to the globe.
 
-The marks are the single-path form of the [Simple Icons](https://simpleicons.org) set (CC0-1.0), drawn on that set's 24×24 viewBox and filled with `currentColor` like every other link glyph, so they follow the link color in both themes, hover, and focus. Every mark is `aria-hidden`; the anchor's own text remains the accessible name.
+The artwork is the [Simple Icons](https://simpleicons.org) set (CC0-1.0) consumed as a devDependency of ui-primitives, so adding a site is one host-map entry and the artwork version comes from the lockfile rather than a copied path. Each mark renders as that set's single path on its 24-unit viewBox, inset to a 28-unit box so it keeps the ~8% margin the 20-unit `ic_ds_*` glyphs draw inside their own box, and takes the link's `currentColor` rather than the brand fill the set records. Every mark is `aria-hidden`; the anchor's own text remains the accessible name.
+
+Taking `currentColor` is deliberate for a functional link glyph rather than brand presentation: YouTube, X, and other brands publish guidelines against recoloring their marks, and the first alternative below weighs the fixed-brand-fill option. CC0-1.0 covers the path data; the marks remain their owners' trademarks, and the generated third-party notices cover the package rather than this artwork.
 
 Two consumers pass their destination: the markdown renderer's `renderSafeLink`, which covers authored anchors, reference links, and URL-promoted inline code, and the web card's source and fetch links. Both already hold the sanitized destination.
 
 ## Alternatives considered
 
-- **Fetching each site's favicon** from the site itself or a favicon service. This covers arbitrary sites, but rendering a transcript would issue network requests to every linked host, which discloses reading activity and turns a text render into a network operation; it also fails offline and behind a strict `img-src` policy, and needs a new external dependency. Rejected: a fixed local vocabulary keeps rendering deterministic and private, and the globe remains the honest fallback.
+- **Fetching each site's favicon** from the site itself or a favicon service. This covers arbitrary sites, but rendering a transcript would issue network requests to every linked host, which discloses reading activity and turns a text render into a network operation; it also fails offline and behind a strict `img-src` policy. Rejected: a fixed local vocabulary keeps rendering deterministic and private, and the globe remains the honest fallback.
 - **Filling the mark with the site's brand color.** Rejected: the link glyphs are `currentColor`-only so they follow the link alias, dark mode, and hover; a fixed fill would be the first exception and would fight the link's own hover color.
+- **Copying the path data into this module.** Rejected: a third-party artwork set that changes upstream is exactly what a pinned dependency tracks; fourteen embedded copies would leave no update path.
 - **Adding site values to `LinkIconKind`.** Rejected: the kind is the category the consumer states, while the site is derived from the destination; folding both into one union would make every consumer spell out a site it does not know.
 - **A monogram tile for every unmapped host.** Rejected as noise: a generated letter capsule claims a site identity without carrying one, and 14px leaves no room for readable initials.
 
 ## Consequences
 
-- Adding a site is one path entry and one host entry in `SiteGlyph.tsx`; the `link-icon` spec pins one host per mapped site and requires their marks to be distinct, so a dropped or duplicated path fails the suite.
+- Adding a site needs a host-map entry in `SiteGlyph.tsx` and, to be checked, a URL in the `link-icon` spec: that spec lists a representative URL per mapped site, requires fourteen distinct marks across them, and pins the GitHub, npm, YouTube, Wikipedia, and X aliases. A dropped or duplicated entry fails the count, while a site added to the map alone stays uncovered by the spec.
+- `simple-icons` enters the browser build graph as a development dependency of ui-primitives; the shell build tree-shakes the fourteen imported marks instead of the complete set.
 - Unknown hosts, non-http schemes, unparseable destinations, and file categories keep the existing globe or category glyph; the `mailto` link in a transcript still shows the globe.
 - The vocabulary is deliberately finite. Sites such as `example.com` never get a mark from this mechanism; recognizing them would require the network fetching the alternatives reject.
-- Coverage: the LinkIcon spec covers aliases, the fallback branches, and the sizing seat; the markdown spec pins one known and one unknown anchor; the web-card spec pins a source and a fetch link against the same marks.
+- Coverage: the LinkIcon spec covers the aliases, the fallback branches, and the sizing seat; the markdown spec pins one known and one unknown anchor; the web-card spec pins a source and a fetch link against the same marks.

+ 8 - 4
.agents/notes/implemented/feature/2026-09-16-known-site-link-marks.zh.md

@@ -12,22 +12,26 @@ Status: implemented
 
 `LinkIcon` 接受可选的 `href`。对于 `url` 类别,目的地主机属于已知站点时改画该站点自己的标记;其余 `url` 目的地仍使用地球,文件类别忽略 `href`,因为它们的目的地是路径而不是站点。
 
-ui-primitives 的 `SiteGlyph.tsx` 持有该映射:十八个主机后缀解析为十四个标记——GitHub(`github.com`、`github.io`、`raw.githubusercontent.com`)、GitLab、npm、PyPI、Stack Overflow、MDN、Wikipedia、Hacker News、YouTube(`youtube.com`、`youtu.be`)、X(`x.com`、`twitter.com`)、Bilibili、知乎、掘金与 CSDN。去掉 `www.` 后,主机等于某后缀或为其子域即匹配,因此 `gist.github.com` 与 `en.wikipedia.org` 无需单独登记。只有绝对 `http:` 与 `https:` 目的地能够匹配,其余一律回退到地球。
+ui-primitives 的 `SiteGlyph.tsx` 持有该映射:十八个主机后缀解析为十四个标记——GitHub(`github.com`、`github.io`、`raw.githubusercontent.com`)、GitLab、npm、PyPI、Stack Overflow、MDN、Wikipedia、Hacker News(`news.ycombinator.com`)、YouTube(`youtube.com`、`youtu.be`)、X(`x.com`、`twitter.com`)、Bilibili、知乎、掘金与 CSDN。主机等于某后缀或为其子域即匹配,因此 `gist.github.com` 与 `en.wikipedia.org` 无需单独登记。只有绝对 `http:` 与 `https:` 目的地能够匹配,其余一律回退到地球。
 
-标记取自 [Simple Icons](https://simpleicons.org) 图标集的单路径形式(CC0-1.0),沿用该图标集的 24×24 viewBox,并与其他链接图形一样以 `currentColor` 填充,因此会跟随两种主题、hover 与 focus 下的链接颜色。所有标记均为 `aria-hidden`,锚点自身文本仍是可访问名称。
+图形取自 [Simple Icons](https://simpleicons.org) 图标集(CC0-1.0),以 ui-primitives 的 devDependency 形式引入,因此新增站点只是主机映射中的一条记录,图形版本由 lockfile 固定,而不是复制路径数据。每个标记以该图标集的单条 path 绘制在其 24 单位 viewBox 上,并内缩到 28 单位盒中,以保留 20 单位 `ic_ds_*` 图形在自身盒内约 8% 的留白;填充使用链接的 `currentColor`,而不是图标集记录的品牌填充色。所有标记均为 `aria-hidden`,锚点自身文本仍是可访问名称。
+
+对功能性链接图形采用 `currentColor` 是刻意的取舍,而非品牌呈现:YouTube、X 等品牌公布的规范禁止为其标记改色,下方第一个备选方案比较了固定品牌填充的做法。CC0-1.0 覆盖路径数据;标记仍属各自所有者的商标,生成的第三方声明覆盖该 npm 包本身,而不是这份图形。
 
 两处消费者传入自己的目的地:markdown 渲染器的 `renderSafeLink`(覆盖作者书写的锚点、引用式链接与提升为链接的行内代码)以及 web 卡片的来源与抓取链接。两者本已持有经安全校验的目的地。
 
 ## Alternatives considered
 
-- **抓取各站点的 favicon**,无论取自站点本身还是 favicon 服务。这能覆盖任意站点,但渲染一份转写内容会向每个被链接的主机发起网络请求,泄露阅读行为并把文本渲染变成网络操作;它在离线与严格 `img-src` 策略下同样失败,且需要新增外部依赖。拒绝:固定的本地词汇表让渲染保持确定与私密,地球仍是诚实的回退。
+- **抓取各站点的 favicon**,无论取自站点本身还是 favicon 服务。这能覆盖任意站点,但渲染一份转写内容会向每个被链接的主机发起网络请求,泄露阅读行为并把文本渲染变成网络操作;它在离线与严格 `img-src` 策略下同样失败。拒绝:固定的本地词汇表让渲染保持确定与私密,地球仍是诚实的回退。
 - **以站点品牌色填充标记。** 拒绝:链接图形只使用 `currentColor`,以跟随链接 alias、暗色模式与 hover;固定填充会成为首个例外,并与链接自身的 hover 颜色冲突。
+- **把路径数据复制进本模块。** 拒绝:上游会持续更新的第三方图形集正是需要固定版本依赖来跟踪的内容;十四个内嵌副本没有更新路径。
 - **把站点值加进 `LinkIconKind`。** 拒绝:kind 是消费者陈述的分类,而站点由目的地推导;把两者折进同一联合类型会迫使每个消费者写出自己并不知晓的站点。
 - **为每个未映射主机生成首字母方块。** 以噪音为由拒绝:自动生成的字母胶囊宣称了一种站点身份却没有承载它,14px 也没有留下可读字母的空间。
 
 ## Consequences
 
-- 新增站点就是在 `SiteGlyph.tsx` 中加一条路径与一条主机记录;`link-icon` spec 为每个已映射站点固定一个主机并要求它们的标记互不相同,因此路径丢失或重复会让测试失败。
+- 新增站点需要在 `SiteGlyph.tsx` 的主机映射中加一条记录,并在 `link-icon` spec 中补一个 URL 才会被检查:该 spec 为每个已映射站点列出一个代表 URL、要求它们共产生十四个互不相同的标记,并固定 GitHub、npm、YouTube、Wikipedia 与 X 的别名。记录丢失或重复会让数量断言失败,而只改主机映射、不补 spec URL 的新站点仍未被 spec 覆盖。
+- `simple-icons` 作为 ui-primitives 的开发依赖进入浏览器构建图;shell 构建会 tree-shake 这十四个导入的标记,而不是整套图标。
 - 未收录主机、非 http scheme、无法解析的目的地与文件类别都保留原有的地球或分类图形;转写内容中的 `mailto` 链接仍显示地球。
 - 该词汇表刻意有限。像 `example.com` 这样的站点不会由此机制获得标记;要识别它们就需要被上述备选方案拒绝的网络抓取。
 - 覆盖:LinkIcon spec 覆盖别名、各回退分支与尺寸座位;markdown spec 固定一个已知锚点与一个未知锚点;web 卡片 spec 用同一批标记固定一个来源链接与一个抓取链接。

+ 1 - 0
THIRD_PARTY_NOTICES.md

@@ -110,6 +110,7 @@ External packages installed for runtime use or distributed inside the prebuilt b
 | [`semver`](https://github.com/npm/node-semver) | ISC |
 | [`sharp`](https://github.com/lovell/sharp) | Apache-2.0 |
 | [`shiki`](https://github.com/shikijs/shiki) | MIT |
+| [`simple-icons`](https://github.com/simple-icons/simple-icons) | CC0-1.0 |
 | [`supports-color`](https://github.com/chalk/supports-color) | MIT |
 | [`tsx`](https://github.com/privatenumber/tsx) | MIT |
 | [`turndown`](https://github.com/mixmark-io/turndown) | MIT |

+ 1 - 1
apps/web/tests/clickable-links-gallery.e2e.ts

@@ -58,7 +58,7 @@ const INERT_SOURCE_URL = 'ftp://mirror.example.test/spec'
 const FETCH_URL = 'https://docs.example.test/tokens'
 // A mapped host, so the prose pins that the leading glyph is the site's mark
 // rather than the globe the unmapped docs host keeps.
-const REPO_URL = 'https://github.com/deepseek-harness/deepseek-harness'
+const REPO_URL = 'https://github.com/example/link-gallery'
 
 /** One-part text content for a built message. */
 function text(value: string): { type: 'text'; text: string }[] {

+ 1 - 1
apps/web/tests/expected/clickable-links-gallery/ui.expected.md

@@ -145,7 +145,7 @@
 - paragraph:
   - text: "Upstream:"
   - link "the repository":
-    - /url: https://github.com/deepseek-harness/deepseek-harness
+    - /url: https://github.com/example/link-gallery
   - text: .
 - paragraph:
   - text: "Inert contrasts:"

+ 2 - 2
packages/client/ui-primitives/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
-README.md: 9b92c06174912509abaeb01f4091701eb1c2663b
-README.zh.md: 80481c01f4c3609855286188a0df97e82dfd17b0
+README.md: 2b13cbf8bc529e3924f45ae27e778c095ee2ae90
+README.zh.md: d1cbc11d43d54b6725a098644d8fca5ae97b3e78

+ 1 - 0
packages/client/ui-primitives/README.md

@@ -146,6 +146,7 @@ None; this package neither assembles nor sends a provider request.
 These limits define how the atoms behave at the edges; they are current package constraints, not a component roadmap.
 
 - **Diff search is bounded, input processing is linear** — the edit-distance limit trades precise alignment for a coarse replacement on heavily changed fragments. Normalization, fallback rows, and copied output still scale with input size; the height cap limits visible rows, not those allocations.
+- **Known-site marks are a fixed list** — only the named hosts resolve to their own mark, and every other external host keeps the globe; recognizing an arbitrary site would require fetching its icon over the network.
 - **Streaming defers cross-boundary reference resolution** — a reference-style link or footnote whose definition sits on the other side of the incremental freeze boundary renders as literal text while the reply streams; the settled full parse at finalize resolves it.
 - **A long highlighted fence retains its complete token DOM** — streaming avoids re-parsing, re-tokenizing, and reconciling the completed prefix, but it does not discard old colors or virtualize token spans. Final DOM cardinality therefore still follows the fence's token count; nested/container fences and a pathological single long line remain on the general tail path.
 - **Glyph-level icons are redrawn approximations** — the fish logo and the sparkle mark come from font glyphs whose vector geometry is not exportable from the local design data; hand-authored recreations stand in until an exact export path exists.

+ 1 - 0
packages/client/ui-primitives/README.zh.md

@@ -146,6 +146,7 @@ kind: "package-library"
 这些限制说明原子组件在边缘情况下的行为;它们是当前包约束,不是组件路线图。
 
 - **Diff 搜索有上限,输入处理仍为线性**:编辑距离上限使大量改动的片段采用粗粒度替换,不再精确对齐。规范化、回退行及复制内容仍随输入大小增长;高度限制只约束可见行数,不限制这些分配。
+- **已知站点标记是固定列表**:只有列名的主机解析为自己的标记,其余外部主机仍使用地球;要识别任意站点需要通过网络抓取它的图标。
 - **流式期间跨边界引用解析被推迟**:定义落在增量冻结边界另一侧的引用式链接或脚注,在回复流式输出期间渲染为字面文本;定稿时的全量解析会将其解析。
 - **长高亮 fence 会保留完整 token DOM**:流式路径避免重新解析、重新 tokenize 和 reconcile 已完成前缀,但不会丢弃旧颜色或虚拟化 token span。因此最终 DOM 数量仍随 fence 的 token 数增长;嵌套/容器内 fence 与病态的单个超长行仍走通用尾部路径。
 - **字形级图标是重新绘制的近似版本**:鱼形标志与闪光标记来自字体字形,而本地设计数据无法导出其矢量几何;在获得精确导出路径前,使用手工重建版本代替。

+ 2 - 1
packages/client/ui-primitives/package.json

@@ -46,7 +46,8 @@
     "mdast-util-gfm": "^3.1.0",
     "mdast-util-math": "^3.0.0",
     "micromark-extension-gfm": "^3.0.0",
-    "micromark-util-sanitize-uri": "^2.0.1"
+    "micromark-util-sanitize-uri": "^2.0.1",
+    "simple-icons": "16.31.0"
   },
   "files": [
     "lib/index.js",

Plik diff jest za duży
+ 5 - 22
packages/client/ui-primitives/src/SiteGlyph.tsx


+ 8 - 5
packages/client/ui-primitives/tests/link-icon.client.spec.tsx

@@ -64,10 +64,11 @@ describe('LinkIcon', () => {
   })
 })
 
-/** One host per mapped site, plus the aliases that must resolve to a mark already listed. */
+/** One host per mapped site, plus three aliases that must resolve to a mark already listed. */
 const SITE_URLS: string[] = [
   'https://github.com/org/repo',
   'https://gist.github.com/abc',
+  'https://org.github.io/repo',
   'https://raw.githubusercontent.com/org/repo/main/a.ts',
   'https://gitlab.com/org/repo',
   'https://www.npmjs.com/package/x',
@@ -77,7 +78,7 @@ const SITE_URLS: string[] = [
   'https://en.wikipedia.org/wiki/Harness',
   'https://news.ycombinator.com/item?id=1',
   'https://youtu.be/dQw4w9WgXcQ',
-  'https://twitter.com/x',
+  'https://x.com/x',
   'https://www.bilibili.com/video/BV1',
   'https://zhuanlan.zhihu.com/p/1',
   'https://juejin.cn/post/1',
@@ -95,14 +96,16 @@ describe('LinkIcon site marks', () => {
     const globe = render(<LinkIcon kind="url" />).container.querySelector('path')!.getAttribute('d')
     const marks = SITE_URLS.map(glyphPath)
     expect(marks).not.toContain(globe)
-    // Sixteen destinations, fourteen mapped sites: the GitHub aliases and the
-    // X alias share their site's mark, and any other duplicate is a mapping bug.
+    // Seventeen destinations, fourteen mapped sites: the three extra GitHub
+    // hosts share the GitHub mark, and any fifteenth mark means the site list
+    // grew without this expectation moving with it.
     expect(new Set(marks).size).toBe(14)
   })
 
   it('resolves the aliases of one site to the same mark', () => {
     const github = glyphPath('https://github.com/org/repo')
     expect(glyphPath('https://gist.github.com/abc')).toBe(github)
+    expect(glyphPath('https://org.github.io/repo')).toBe(github)
     expect(glyphPath('https://raw.githubusercontent.com/org/repo/main/a.ts')).toBe(github)
     expect(glyphPath('https://www.npmjs.com/package/x')).toBe(glyphPath('https://npmjs.com/package/x'))
     expect(glyphPath('https://en.wikipedia.org/wiki/Harness')).toBe(glyphPath('https://www.wikipedia.org'))
@@ -116,7 +119,7 @@ describe('LinkIcon site marks', () => {
     ['a host that only ends in a mapped name', 'https://notgithub.com/a'],
     ['a non-http scheme', 'mailto:owner@example.com'],
     ['a destination that is not a URL', 'src/index.ts'],
-    ['a hostless URL', 'https:///a'],
+    ['a single-label host', 'https:///a'],
   ])('keeps the globe for %s', (_case, href) => {
     const globe = render(<LinkIcon kind="url" />).container.querySelector('svg')!.outerHTML
     expect(render(<LinkIcon kind="url" href={href} />).container.querySelector('svg')!.outerHTML).toBe(globe)

+ 2 - 3
packages/client/ui-primitives/tests/web-block.client.spec.tsx

@@ -2,8 +2,7 @@
 
 import { afterEach, describe, expect, it } from 'vitest'
 import { cleanup, render } from '@testing-library/react'
-import { WebBlock as LocalizedWebBlock } from '../src/index.ts'
-import { LinkIcon } from '../src/index.ts'
+import { LinkIcon, WebBlock as LocalizedWebBlock } from '../src/index.ts'
 import type {
   WebFetchBlockProps, WebSearchBlockProps, WebSourceView,
 } from '../src/index.ts'
@@ -109,7 +108,7 @@ describe('WebBlock search card', () => {
     expect(anchor.getAttribute('rel')).toBe('noopener noreferrer')
   })
 
-  it('leads a source and a fetched url on a known site with that site mark', () => {
+  it("leads a source and a fetched url on a known site with that site's mark", () => {
     /** The mark a link leads with, as its path data. */
     const mark = (element: Element): string | null => element.querySelector('a svg path')!.getAttribute('d')
     const view = render(<WebBlock kind="search" truncated={false} sources={[

+ 14 - 5
pnpm-lock.yaml

@@ -3746,6 +3746,9 @@ importers:
       shiki:
         specifier: ^4.3.1
         version: 4.3.1
+      simple-icons:
+        specifier: 16.31.0
+        version: 16.31.0
 
   packages/client/ui-reference:
     devDependencies:
@@ -7993,7 +7996,7 @@ importers:
         version: link:../../../vendor/schemastery
       '@earendil-works/pi-ai':
         specifier: ^0.85.1
-        version: 0.85.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+        version: 0.85.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(supports-color@9.4.0)(ws@8.21.0)(zod@4.4.3)
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -18798,6 +18801,10 @@ packages:
   simple-get@4.0.1:
     resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
 
+  simple-icons@16.31.0:
+    resolution: {integrity: sha512-dDEqvQrmxL7ZnqQtGHGjCNW6CfiwzAif+83Hol9JXAnAB6wh2zsZD7Sc1kWT0O/RRAKt7xfG8W6UZhYBPD+GAA==}
+    engines: {node: '>=0.12.18'}
+
   simple-update-notifier@2.0.0:
     resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
     engines: {node: '>=10'}
@@ -20344,14 +20351,14 @@ snapshots:
     transitivePeerDependencies:
       - '@algolia/client-search'
 
-  '@earendil-works/pi-ai@0.85.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
+  '@earendil-works/pi-ai@0.85.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(supports-color@9.4.0)(ws@8.21.0)(zod@4.4.3)':
     dependencies:
       '@anthropic-ai/sdk': 0.123.0(zod@4.4.3)
       '@aws-sdk/client-bedrock-runtime': 3.1048.0
       '@earendil-works/pi-telemetry': 0.85.1
       '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))
       '@smithy/node-http-handler': 4.7.3
-      http-proxy-agent: 7.0.2
+      http-proxy-agent: 7.0.2(supports-color@9.4.0)
       https-proxy-agent: 7.0.6
       openai: 6.40.0(ws@8.21.0)(zod@4.4.3)
       partial-json: 0.1.7
@@ -22971,7 +22978,7 @@ snapshots:
       cross-spawn: 7.0.6
       debug: 4.4.3(supports-color@9.4.0)
       fs-extra: 10.1.0
-      http-proxy-agent: 7.0.2
+      http-proxy-agent: 7.0.2(supports-color@9.4.0)
       https-proxy-agent: 7.0.6
       js-yaml: 4.3.1
       sanitize-filename: 1.6.4
@@ -24268,7 +24275,7 @@ snapshots:
       statuses: 2.0.2
       toidentifier: 1.0.1
 
-  http-proxy-agent@7.0.2:
+  http-proxy-agent@7.0.2(supports-color@9.4.0):
     dependencies:
       agent-base: 7.1.4
       debug: 4.4.3(supports-color@9.4.0)
@@ -26077,6 +26084,8 @@ snapshots:
       once: 1.4.0
       simple-concat: 1.0.1
 
+  simple-icons@16.31.0: {}
+
   simple-update-notifier@2.0.0:
     dependencies:
       semver: 7.8.5

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików