suzmii 3de2ec734b 预览门禁去掉单点依赖:observer 必定建出,并加可见性兜底扫描
真机现场:整个「UI 交互」栏(51 项)全部只剩占位图标。根因链是**同一对文件里的两个缺陷叠加**,因此作为一个原子提交收口(拆成两半任一半都不足以独立成立:光修 observer 仍可能被回调时序漏掉,光加扫描则 observer 根本不存在时的判定精度无从恢复)。

一、确认 2a157ea6f 引入的回归:observer 可能永远不建
- 那次改动把「root 为 null」当成「不建 observer」,并指望「卡片注册」作为 root 就绪信号重建。
- 但那个 effect 每个 scope **实际只跑一次**(`requestPreview` 是稳定引用,其余依赖在 scope 内不变),一旦这一轮因 root 为 null 直接返回就再无第二次机会;
- 而「卡片注册」信号只在**后续还有新注册**时才来 —— 初次挂载时 51 张卡一次性注册完,之后再没有新注册,信号永不再来。
- 结果:observer 永不创建、所有卡永不被观察、永远停在 `idle`,整栏只剩占位图标(`categoryIcons['ui-interaction'] = LayoutGrid`)。
- 修复:**绝不拿"root 未就绪"当作"不建 observer"的理由**。拿到容器 root 最好;拿不到就退到 canvas、再退到视口(`root: null`)。视口判定严格优于"没有 observer"——屏幕上真实可见的卡一定落在视口矩形内;代价只是视口外但仍在画布内的卡晚一步由滚动触发。容器 root 迟到时再有界重试(5 次、退避到 512ms)重建一次,恢复按画本容器裁剪的精确判定。

二、系统性防线:可见性兜底扫描(去掉单点依赖)
- 门禁原先只有 `IntersectionObserver` 一条路,observer 没建出来 / 回调没送达 / 注册与创建交错,卡片就再无第二次机会。
- 新增 `sweepVisiblePreviews()`:按**与 observer 对齐的视口几何判据**(含 160px 余量)独立复核登记表,只对 `idle`(从未请求)的卡补一次 `visible`,`loading`/`queued`/`loaded`/`failed` 一律不碰。
- **不是第二条加载通路**:仍经 `requestPreview` 走同一条队列,受同一套去重、优先级、全局 3 槽与 `48` 项 / `64 MiB` 预算约束;**只补"可判定为可见"的卡,不退化成全量预读**(PRD §3.3.2 合同不变)。
- 触发点:scope 变化后 0 / 250 / 1000ms 各扫一次(覆盖晚挂载的卡)、observer 就绪后立即扫一次、每次卡片注册时扫一次、窗口 resize 与页面重新可见时各扫一次。

测试(`tests/useProjectResourceCardPreviews.test.ts`)
- 更新「等真正的 root 就绪」这条契约:它原先断言"root 为 null 时不得建 observer"——那正是本轮回归的语义,改为断言"**仍然必须建出 observer**(`root: null` 视口判定)",并要求容器 root 迟到后重建为容器 root;
- 新增「不在热预取窗口内的可见卡不得停在 idle」(20 张 / 热预取 12);
- 新增「没有任何 observer 回调时,可见卡也必须被放行」——完全不触发回调,只靠几何可见性,专门守住兜底扫描;
- 新增「视口外的已登记卡不得被请求」——守住"不退化成全量预读"。

变异验证:把兜底扫描改成直接 `return 0`(等价于去掉扫描)→ 「没有任何 observer 回调时可见卡也必须被放行」立即失败(`expected undefined to be 'loaded'`);恢复后 21/21 通过。断言不是恒真假守卫。

可观测性:`data-preview-status` / `data-preview-error` 已在位,因此"可见却 idle"这类问题以后读一个 DOM 属性即可判定,不再需要解码图片或猜调度。

验证(同一时刻、同一命令的前后对照;树上另有并发改动):改动前 `86 files / 1225 passed / 4 skipped / 0 failed`;改动后 `86 files / 1227 passed / 4 skipped / 0 failed`(多出的 2 条为本提交新增用例)。`src/components/image-editor` 1385 passed;typecheck exit 0;check:encoding 4384 文件;prettier 与 eslint 干净;`git diff --check` 干净。
2026-09-11 16:49:09 +08:00
2026-08-21 12:20:43 +08:00
2026-04-26 18:06:23 +08:00
2026-07-07 14:04:08 +08:00
2026-07-16 18:33:06 +08:00
2026-09-01 16:30:15 +08:00
2026-05-08 17:58:33 +08:00
2026-07-08 17:35:45 +08:00
2026-07-17 22:07:52 +08:00
1
2026-05-14 14:21:17 +08:00
2026-09-08 15:11:36 +08:00

Genarrative / 陶泥儿

一个 AI 原生互动内容与小游戏平台,当前主站聚焦图片画布创作、项目与素材管理,以及账号、钱包和后台管理等平台公共能力。

当前已经具备这些主要能力:

  • 图片画布编辑、项目与素材管理
  • 图片、视频、音频等外部生成任务
  • 账号、钱包、充值、兑换码与个人资料
  • 后台运营、配置与生产运维工具
  • AI 游戏创作独立 App

运行

前置条件:

  • Node.js
  • Rust / Cargo
  • SpacetimeDB CLI

安装依赖:

npm install

该命令会按根 package.json 的 npm workspaces 一次安装主站、Admin、Mobile、Desktop、AGC、Preview Deployer、内部 packages 与工具依赖;仓库只使用根 package-lock.json,不要在子目录单独执行 npm install / npm ci 或提交嵌套 lockfile。

准备环境变量:

  • 复制 .env.example.env.local
  • 只填写本次联调所需配置;不要提交 .env.local、密钥、Token 或其它本地认证信息
  • api-server 环境变量和 Provider 配置以 .env.example 及当前开发运维文档为准

启动开发环境:

npm run dev

补充说明:

  • npm run dev 会启动 SpacetimeDB standalone、独立 bgfilter-worker、Rust api-server、主站 Vite 与后台 Vite,适合完整联调;内部 worker ready 后才启动 API。
  • 主站默认地址是 http://127.0.0.1:3000,后台可从 http://127.0.0.1:3000/admin/ 进入,也可直连 http://127.0.0.1:3102
  • 五个模块可独立启动:npm run dev:spacetimenpm run dev:api-servernpm run dev:bgfilter-workernpm run dev:webnpm run dev:admin-web;其中 dev:api-server 会安全带起同 runner 的 BgFilter worker 依赖。
  • 如需自动刷新后端模块,使用 npm run dev -- --watch;其中 spacetime-module 改动后只会重新发布模块,不会重启 standalone,Rust 源码改动会把 api-serverbgfilter-worker 作为一个组合单元重启。主站和后台前端源码变化交给 Vite 自身 HMR,不由外层 watcher 重启。非 watch 模式下可在 npm run dev 终端输入 rs api-serverrs bgfilter-workerrs webrs admin-webrs spacetimers all,其中 rs spacetime 也是只重新发布模块。

构建生产包:

npm run build

常用检查

类型检查:

npm run lint

编码检查:

npm run check:encoding

主要结构

主运行时:

创作与项目能力:

文档入口

docs/ 已在 2026-08-25 按当前代码与运行态重新收口。旧 PRD、设计、审计、阶段计划和技术流水账不再作为实现依据;专题文档的现役清单统一从 docs/README.md 进入:

S
Description
叙世
Readme 758 MiB
Languages
Rust 56.1%
TypeScript 24.4%
JavaScript 10%
Java 3.7%
Go 3.1%
Other 2.6%