news 2026/9/10 20:04:40

OpenHuman 身份市场(Identity Marketplace)卖方能力 Web-only 设计解析:桌面端 Trading 页引导到 tiny.place 的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHuman 身份市场(Identity Marketplace)卖方能力 Web-only 设计解析:桌面端 Trading 页引导到 tiny.place 的完整实现

OpenHuman 身份市场(Identity Marketplace)卖方能力 Web-only 设计解析:桌面端 Trading 页引导到 tiny.place 的完整实现

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

导读

本文基于 OpenHuman 仓库中的设计规格文档 docs/superpowers/specs/2026-07-24-identity-marketplace-seller-web-only-design.md 及其配套实施计划 docs/superpowers/plans/2026-07-24-identity-marketplace-seller-web-only.md,完整拆解"身份市场卖方能力仅限 Web 端(seller-side is web-only by design)"这一架构决策:从问题背景、决策依据、UI 引导实现、openUrl跨端打开链路、测试策略到回滚方案,并辅以仓库内真实源码(如 app/src/utils/openUrl.ts)佐证底层原理。读完本文,你将掌握"如何通过最小化的纯前端改动消除产品死胡同、并在仓库内固化架构边界"这一类问题的完整解题范式。

问题背景:桌面端身份市场只有"买方"通路

在 OpenHuman 桌面应用中,身份市场(Identity Marketplace)位于Agent World → Identities → Trading标签页。该标签页在设计之初只实现了**买方侧(buyer-side)**动作:

  • 浏览在售列表(browse listings)
  • 购买(buy)
  • 出价(bid)
  • 发起报价(make an offer)

而**卖方侧(seller-side)**动作——将自己的@handle上架出售(list for sale)、接受/拒绝收到的报价或出价(accept / reject offer)——在桌面端完全没有实现路径。用户一旦想出售身份,打开 Trading 标签页便会走到一条死胡同(dead-end)。这正是 issue #4920 描述的核心矛盾:"Identity marketplace is buyer-only: no list-for-sale or accept/reject-offer path",属于 epic #4776 §9(Trading / Identities marketplace)的子项。

这一问题的根源不在 UI 层,而在服务端与 SDK 层:

  1. tiny.place 后端不暴露卖方路由(create-listing、accept/reject-offer),即服务端根本没有对应的 API;
  2. 仓库内 vendor 的 Rust SDK(设计文档中指vendor/tinyplace/sdk/rust/src/api/marketplace.rs)被明确标注为"compatibility wrapper for marketplace endpoints used by OpenHuman"——一个仅覆盖买方场景的兼容性封装。

因此,上架身份与响应报价这类操作的真实落点,是tiny.place 的 Web 应用,而非桌面客户端。

核心决策:Seller-side actions are web-only by design

面对 #4920,存在两条截然不同的解决路线:

  • 路线 A(重型):补齐完整的卖方技术栈——vendor SDK 卖方方法 → Rust core 卖方 handler(handle_tinyplace_marketplace_*)→invokeApiClient.ts卖方方法 → UI。这是一条"SDK → core handler → invokeApiClient → UI"的全链路实现,成本高且受制于后端能力。
  • 路线 B(务实):接受现状,把"卖方能力是 Web-only"作为**明确的设计决策(by design)**固化下来,并消除桌面端的死胡同。

设计文档选择了路线 B,其落地方式由三件事构成:

  1. 移除应用内死胡同:在 Trading 标签页加入一条清晰的提示与跳转入口,把用户引导到 Web 流程;
  2. 在仓库内记录该范围决策:通过两处文档注释(前端组件头部 docstring + Rust 领域模块 doc)让"Web-only"成为可检索、可追溯的架构事实;
  3. 把 #4776 §9 的对应条目标记为 N/A:在 epic 上明确"不做"而非"未做"。

为什么是"决策"而不是"临时绕过"

值得强调的是,文档用by design而非 workaround 来定性这一方案。理由有三:

  • 后端没有卖方路由,这是服务端能力边界的事实;
  • vendored SDK 的定位就是买方兼容层,扩展它属于 SDK 厂商(tiny.place)的责任边界;
  • 卖方流程(上架、议价、成交)天然适合在 Web 应用内完成,桌面端重复实现既无必要也无收益。

将结论写入代码注释,等于把"当时为什么这样定"的知识沉淀进仓库,避免后来者再次踩进"补全卖方栈"的陷阱。

实施方案拆解(Scope)

1. UI 引导:Trading 标签页的SellOnWebNote

核心改动位于app/src/agentworld/pages/IdentitiesSection.tsx的 Trading 标签页:在 listings / recent-sales 区域下方追加一条常驻提示信息(persistent info note):

ℹ️ Selling a handle or responding to offers happens on tiny.place.[Open tiny.place →]

配套实施计划给出了完整的实现细节,包括组件常量与 JSX 块:

// Seller-side identity actions (list a handle for sale, accept/reject an offer) // are web-only — the tiny.place backend exposes no seller routes and the // vendored SDK is a buyer-side compatibility wrapper (see #4920). We point // sellers at the web app instead. Hardcoded prod URL, matching the // `FUND_PAGE_URL` precedent in X402ConfirmDialog.tsx. const SELL_ON_WEB_URL = 'https://tiny.place/identities';
{/* Seller-side is web-only (#4920): no in-app list-for-sale / accept-offer path exists, so point sellers at the tiny.place web app. */} <div className="rounded-lg border border-line bg-surface-muted/40 p-3" >* * Seller-side actions (list a handle for sale, accept / reject an offer/bid) * are intentionally NOT in-app — they are web-only on tiny.place. The backend * exposes no seller routes and the vendored SDK is a buyer-side compatibility * wrapper, so the Trading tab links sellers to the web app instead (#4920).
  • 后端:在 tinyplace 核心领域模块(设计文档指向src/openhuman/tinyplace/mod.rs)的模块 doc 中追加一节:
//! //! ## Marketplace scope (buyer-side only) //! //! The identity marketplace handlers here are **buyer-side only**: buy a //! listing, bid, and make an offer. Seller-side actions — listing a handle for //! sale and accepting / rejecting offers — are **web-only on tiny.place** (the //! backend exposes no seller routes). The desktop Trading tab links sellers to //! the web app instead. See #4920.

后者是纯文档注释,不产生任何代码行为变化,可通过cargo checkcargo fmt验证(doc comment 不会被 reflow)。

4. 测试与覆盖率门禁

新增改动受仓库"变更行覆盖率 ≥80%"门禁约束,因此必须配套测试。设计在IdentitiesSection.test.tsx中新增两个 Vitest 用例:

describe('Trading tab — seller web-only note', () => { test('renders the seller pointer note on the Trading tab', async () => { render(<IdentitiesSection />); await gotoTab('Trading'); const note = await screen.findByTestId('sell-on-web'); expect(note).toHaveTextContent(/selling a handle or responding to offers/i); }); test('CTA opens the tiny.place identities page via openUrl', async () => { render(<IdentitiesSection />); await gotoTab('Trading'); await userEvent.click(await screen.findByTestId('sell-on-web-cta')); expect(vi.mocked(openUrl)).toHaveBeenCalledWith('https://tiny.place/identities'); }); });

配套的openUrlmock 需要在测试文件顶部注册,确保断言的是"CTA 调用了openUrl且参数正确",而不是真正触发 Tauri 系统调用:

// External-link opener — assert the seller CTA hands off to the OS browser // without actually invoking Tauri. vi.mock('../../utils/openUrl', () => ({ openUrl: vi.fn() }));

实施计划还给出了标准的 TDD 流程(先写失败测试 → 实现 → 验证通过)与验证命令:

# Step 3:验证新测试先失败 pnpm --filter openhuman-app test -- IdentitiesSection --run -t "seller web-only" # Step 7:验证新测试通过且不破坏既有用例 pnpm --filter openhuman-app test -- IdentitiesSection --run

5. GitHub 簿记(外联动作)

第 3 个任务是 GitHub 侧的书签式记录,不属于代码改动:在 #4920 上评论 Web-only 的解决方案,并将 #4776 §9 中的"Sell / list a handle for sale""Accept / reject offer"两行状态改为N/A (web-only — #4920),把- [ ] Sell / list flow works勾选为- [x] Sell / list flow works — N/A, web-only (#4920)。这些动作通过ghCLI 完成,并在 PR 正文中注明,属于对外可见的操作,需要用户显式确认后再执行。

明确不做的事(YAGNI 边界)

设计文档用专门的 "Out of scope (YAGNI)" 小节划清了边界,这本身就是一次很好的"范围管理示范":

  • vendor SDK 的卖方方法(create_listing/accept_offer/reject_offer);
  • Rust core 的handle_tinyplace_marketplace_*卖方 handler;
  • invokeApiClient.ts的卖方方法;
  • IdentitiesSection.tsx的整文件 i18n 迁移(新字符串保持硬编码英文)。

组件与数据流:一次"零网络"的浏览器移交

改动后的数据流可以用下面这张图完整表达:

Trading tab (IdentitiesSection.tsx) └─ SellOnWebNote ──click──▶ openUrl('https://tiny.place/identities') └─ tauri-plugin-opener → OS default browser

值得注意的是:本次改动不引入任何 core RPC,也不产生应用自身的网络请求——CTA 只是把控制权移交给操作系统默认浏览器。这从架构上保证了改动的低风险:应用进程内没有任何新的攻击面或错误路径。

错误处理:openUrl的 CEF IPC 桥接缺口与回退

设计文档强调"openUrl()已经处理了 CEF IPC-bridge 缺口,因此本改动不引入新的错误面"。要理解这句话,需要读一下 app/src/utils/openUrl.ts 的真实实现:

export const openUrl = async (url: string): Promise<void> => { const normalizedUrl = url.trim(); if (isTauri()) { try { await tauriOpenUrl(normalizedUrl); return; } catch (err) { Sentry.addBreadcrumb({ category: 'ipc', level: 'warning', message: 'tauriOpenUrl failed; evaluating fallback', data: { url: getTelemetryUrl(normalizedUrl), error: String(err) }, }); if (!isHttpUrl(normalizedUrl)) { throw err; } // http(s) URL — safe to fall back to window.open. } } window.open(normalizedUrl, '_blank', 'noopener,noreferrer'); };

这里的三个关键机制与本次改动的可靠性直接相关:

  1. CEF 桥接缺口:CEF embedder 中,IPC bridge(window.ipc.postMessage)是在on_after_created之后才注入到 renderer 侧的。如果点击恰好落在这个注入间隙,tauri-plugin-openerinvoke()胶水会以TypeError: Cannot read properties of undefined (reading 'postMessage')拒绝。openUrl对此的应对是:http(s) URL 回退到window.open,保证用户流程不中断。

  2. 非 http(s) 场景直接抛错:对obsidian://这类自定义 scheme,回退到window.open反而会错误地打开一个无法处理自定义 scheme 的 Tauri webview 窗口,因此这种情况下选择向上传播错误,由调用方决定如何展示——这是一种比"静默失败"更好的用户体验。

  3. 低 PII 遥测getTelemetryUrl对 http(s) URL 只保留 origin(host),对mailto:/obsidian://只保留 protocol——路径、查询串、fragment 中可能携带的 token、邮箱、本地路径绝不离开设备。这保证了即便是失败场景,埋点数据也不会泄露敏感信息。

由于本改动复用的正是这条经过生产验证的链路,错误处理成本为零。

测试策略与 E2E 豁免

设计文档给出了清晰的测试分层:

  • 单元测试(Vitest):渲染TradingTab,断言卖家提示文案存在、CTA 携带identities.sellOnWeb分析 ID、点击后以期望 URL 调用 mockedopenUrl
  • 无 Rust 行为变更 → 无需 Rust 测试
  • E2E 明确跳过(approved exception):该引导是一个静态提示条,其 CTA 通过openUrl移交操作系统浏览器,不存在跨进程行为可供桌面 WDIO E2E 断言——单测已经覆盖了全部可验证行为。这被记录为对仓库标准"单元测试 + E2E"期望的批准例外,并在设计文档 Testing 节与测试describe块注释中双重备案。

这一"为什么不需要 E2E"的论证过程,本身就是值得复用的测试决策方法论:E2E 的价值在于跨进程/跨组件集成行为的回归保护,当改动被刻意设计为"无跨进程行为"时,单测就是充分且必要的覆盖手段。

回滚与风险评估

设计文档将风险评级为

  • 改动是纯增量的 UI 提示 + 文档注释;
  • 不改变既有的 buy/bid/offer 流程的任何行为;
  • 不触碰任何 i18n/locale 文件(字符串为硬编码英文,与文件现状一致);
  • 回滚即干净地移除:提示块 + 头部 docstring 注释 + 两个单测用例。

配套实施计划还给出了完整的质量门禁命令(类型检查、lint、格式化):

pnpm typecheck && pnpm --filter openhuman-app lint -- --fix app/src/agentworld/pages/IdentitiesSection.tsx app/src/agentworld/pages/IdentitiesSection.test.tsx && pnpm --filter openhuman-app exec prettier --write app/src/agentworld/pages/IdentitiesSection.tsx app/src/agentworld/pages/IdentitiesSection.test.tsx

以及 Rust 侧文档注释的验证:

GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml cargo fmt --manifest-path Cargo.toml

设计方法论总结:什么时候用"Web-only"收窄客户端范围

这则设计文档的价值不止于一个 UI 提示,它示范了一套可复用的架构决策框架:

  1. 先确认能力边界再决定实现路线:卖方功能的缺失根源在后端路由与 SDK 定位,而非 UI 疏漏——先诊断"哪一层没有能力",避免在错误层级堆代码;
  2. 用"by design"固化现状:把"为什么不做"写进代码注释与领域模块文档,让后来者免于重复调研(这一点也可以对照同仓库的姊妹设计 docs/superpowers/specs/2026-07-30-tinyplace-feed-post-media-web-only-design.md,它采用了同样的 Web-only 收窄模式);
  3. 以最小改动消除产品死胡同:一次纯增量的信息提示 + 浏览器移交,零网络、零新错误面,换来用户路径的完整闭环;
  4. 显式声明 YAGNI 边界:明确列出"不做"清单,防止范围蔓延;
  5. 测试与例外都留下书面理由:单测覆盖行为、E2E 豁免给出论证,让质量门禁既严格又可解释。

对于任何"桌面端功能与服务端/Web 端能力出现职责分歧"的场景——身份市场、内容发布、社交互动等——这套"先界定能力边界,再以最小改动把用户引导到正确落点,最后在仓库内固化决策"的流程都值得直接借鉴。

参考链接

  • 设计规格:docs/superpowers/specs/2026-07-24-identity-marketplace-seller-web-only-design.md
  • 实施计划(含完整代码示例与命令):docs/superpowers/plans/2026-07-24-identity-marketplace-seller-web-only.md
  • 跨端 URL 打开实现:app/src/utils/openUrl.ts
  • 同模式的姊妹设计:docs/superpowers/specs/2026-07-30-tinyplace-feed-post-media-web-only-design.md

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 20:03:12

VCU应用层模型开发与量产编译关键技术解析

1. VCU应用层模型在实车量产中的核心价值在汽车电子电气架构快速迭代的当下&#xff0c;VCU&#xff08;Vehicle Control Unit&#xff09;作为整车控制的"大脑"&#xff0c;其开发模式正经历从传统手写代码向模型化设计的范式转移。应用层模型开发的最大优势在于&am…

作者头像 李华
网站建设 2026/9/10 20:01:15

Wireshark抓包实战:从登录报文分析到MD5弱类型绕过

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 20:01:04

电源设计从入门到实战:拓扑、选型、布局与调试

电源这东西&#xff0c;被叫成“心脏”一点都不夸张。你想想&#xff0c;人没了心脏啥也干不了&#xff0c;电子电路也一样——CPU、运放、传感器、电机&#xff0c;每一颗芯片都需要一个稳定、干净的供电环境。很多刚入门的朋友总觉得电源就是插个电、接个线的事&#xff0c;可…

作者头像 李华
网站建设 2026/9/10 19:56:33

Mac智能切片软件横评:5款真正可用的AI视频自动分段工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华