Skip to content

[P1] 移除 shared Workbench 对具体 HubClient 的隐藏数据层依赖 #1546

Description

@DeliciousBuding

结论

shared UI 的机器门禁目前只禁止 hubClient 的 runtime value import,允许 import type { HubClient }。但 useWorkbenchProjectsRoute 仍直接接受具体 HubClient,并在 shared hook 内执行:

  • list/create/update workspace project API;
  • pagination cursor 管理;
  • loading/error/saving 状态;
  • parent-managed props 与 internal Hub state 双模式切换;
  • mock data fallback。

这虽然没有 runtime import,却仍然把 presentation/workbench 层绑定到一个具体 transport client 的完整方法集合。类型层耦合会迫使 shared route 随 HubClient 扩张,也让 Web/Desktop 的数据 ownership 不明确。

此外 loadMore 对异常使用空 catch,错误完全不可见,滚动 sentinel 会在下次触发时继续重试,用户和监控均不知道分页失败。

实证位置

  • app/shared/src/workbench/useWorkbenchProjectsRoute.ts
    • hubClient?: HubClient
    • shared hook 直接调用 listWorkspaceProjects/createWorkspaceProject/updateWorkspaceProject
    • projects 存在时由 parent 管理,否则内部管理;
    • load-more error 被静默吞掉。
  • scripts/verify/verify-shared-ui-hubclient.ps1
    • 当前 policy 只禁止 value import,并明确允许 type-only concrete HubClient。
  • [P2] Workbench 合同切片:用领域 assembler 收口项目/Agent/市场 prop-bag #1528 正在处理 Workbench domain assembler,本问题应与其设计对齐。

风险

  1. shared UI 表面 transport-neutral,实际对具体 HubClient API 形状耦合;
  2. 同一 route 同时支持 controlled data 与 internal fetching,ownership 难以推断;
  3. Web/Desktop 测试可能走不同分支,造成跨端行为漂移;
  4. concrete client 继续向 Workbench props 扩散;
  5. silent pagination error 形成不可观测的半加载状态;
  6. mock/real/controlled 三套数据路径增加组合爆炸。

目标设计

shared Workbench 只消费窄领域 port/controller,不认识具体 HubClient:

interface ProjectsController {
  state: {
    items: ProjectInfo[];
    activeId: string | null;
    loading: boolean;
    loadingMore: boolean;
    saving: boolean;
    error?: DomainError;
    hasMore: boolean;
  };
  select(id: string): void;
  create(draft: ProjectDraft): Promise<ProjectInfo>;
  update(id: string, draft: ProjectDraft): Promise<ProjectInfo>;
  loadMore(): Promise<void>;
  retry(): Promise<void>;
}
  • Web/Desktop composition root 使用 HubClient 实现该 controller;
  • shared hook 只处理视图选择、filter/tab/preview 等纯 UI 状态;
  • data fetching、pagination、retry、cache ownership 位于平台/domain adapter;
  • 一个 consumer 只能选择一个 ownership mode,不再运行时猜测 projects ?? hubClient ?? mock
  • demo/mock 通过显式 DemoProjectsController 注入。

#1528 的关系

#1528 的 projects assembler 不应只是把 hubClient 塞进 projectsController 后原逻辑不变。

推荐顺序:

  1. [P2] Workbench 合同切片:用领域 assembler 收口项目/Agent/市场 prop-bag #1528 先建立 typed projects model/controller 边界;
  2. 本 Issue 删除 concrete HubClient fallback 和双 ownership;
  3. 更新 shared boundary verifier,禁止 shared UI 类型签名引用 concrete HubClient,仅允许领域 port。

#1528 尚未开工,可将本问题作为其 projects slice 的强制验收项,但 Issue 保持独立用于防止被“props 数下降”掩盖。

必须测试

  • Web 与 Desktop 使用相同 projects controller contract suite;
  • initial load / create / update / load more / retry / error;
  • loadMore 失败在 UI 状态和 telemetry 中可见;
  • 不重复追加相同 page;
  • cursor 变化与 stale response 处理;
  • unmount/cancel 后不写 stale state;
  • demo mode 显式注入,不会在 real mode 泄漏 mock;
  • shared Workbench source 无 concrete HubClient type/value reference。

禁止

  • 建一个 type ProjectsController = Pick<HubClient, ...> 伪装解耦;
  • 把 HubClient 放进 React Context 后继续由 shared 直接调用;
  • 长期保留旧 hubClient prop 与新 controller 双入口;
  • 继续静默吞掉 pagination error;
  • Record<string, unknown> 代替领域合同。

完成条件

  • shared Workbench 不引用具体 HubClient;
  • Projects 数据只有一个明确 owner;
  • Web/Desktop adapter 通过同一 contract tests;
  • pagination failure 可见、可重试、可观测;
  • verifier 能阻止 concrete transport type 再次进入 shared presentation。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions