这是对 Sanity 挑战,路径二:编写奇特事物 的投稿。
我在这个黑客马拉松中构建了一个梦想日志。
几天之后,我在一个充满真实 DEV 文章的巨型图书馆中漫步,调试漂浮的书架,构建空间创作工具,并学习如何让 Sanity 记住哪些书架是活的。
这种升级可能已经告诉你了解这个项目进展所需要知道的大部分信息。
结果是 Oniria:活着的 DEV 图书馆。
与其把 DEV 当作信息流、网格或搜索页面来浏览,Oniria 将其变成一个地方。
- 你在房间中漫步。
- 文章变成书籍。
- 主题占据书架。
- 搜索会实际补货图书馆的部分区域。
- 档案的部分内容会根据活动随时间演变。
- Sanity 会记住该物理空间之前曾占据的内容。

项目背后的问题最终变成了:
如果信息具备地理属性会怎样?
🔗 项目链接
仓库: github.com/miflow13/Oniria
代理会话: 公开的 Oniria 开发记录
Sanity 项目: Oniria 存档控制
实时项目:
TBA
演示视频:TBA
我构建了什么
Oniria 是一个基于实时 DEV 内容构建的可步行 3D 图书馆。
当前图书馆分为六个区域:
| 区域 | 用途 |
|---|---|
| 精选 | 热门且经过策划的文章 |
| 新到 | 最近发布的 DEV 文章 |
| 主题 | 基于标签的合集 |
| 创作者 | 作者及其作品 |
| 搜索 | 实体卡片目录 |
| 存档 | 深入探索和长尾内容 |
DEV 提供实时内容。
Sanity 提供世界的记忆和结构。
Three.js 将两者变成可漫步的空间。
数据流
我不想把 DEV 文章直接复制到 CMS 中,然后称之为集成。
相反,DEV 仍然是内容的来源。
DEV API
│
│ articles / creators / tags / search
▼
Oniria
│
├──────────────► Three.js
│ architecture
│ books
│ navigation
│ interactions
│
▼
Sanity
world configuration
room definitions
layout markers
curator picks
guided journeys
living shelf state
history / lifecycle
DEV 告诉 Oniria 当前实际存在哪些内容。

Sanity 告诉 Oniria 这个世界意味着什么、万物各归何处,以及这里曾经发生过什么。
正是这一区分,奠定了整个项目的根基。
🎮 演示
首次到访的流程刻意保持简单:
- 走进图书馆,探索精选区或新上架区。
- 走近书架,选中一本书。
- 打开那本书所对应的真实 DEV 文章。
- 返回图书馆,落回离开时的同一位置。
- 前往搜索区,向 DEV 发起查询,看着那个房间围绕查询结果重新补满书籍。
- 探索主题区、创作者区,以及更深处的档案馆。
我们的目标是:把底层技术做得足够复杂,同时让心智模型保持简单。
访客不必理解数据图谱、程序化系统、生命周期状态或空间索引。
他们只需要明白一点:
这是一座图书馆。
📸 Oniria 内部一览
主大厅

漫步书架之间
阅读真实的 DEV 文章

Sanity 作为世界后端
使用游戏内创作工具
💻 代码
仓库: github.com/miflow13/Oniria
主要技术栈如下:
- Next.js 16
- React 19
- TypeScript
- Three.js
- Sanity
- next-sanity
- DEV / Forem API
- Vercel
- GLB 环境和建筑资产
建筑本身是通过代码、配置、可重用资产和结构化世界数据来组装的,而不是传统的 3D 关卡编辑器。
这一部分成为项目中最重要的部分之一。
🛠️ 构建 Oniria
这是我对 Oniria 最感兴趣的部分。
该项目并非源自一个完美的提示。
它多次改变方向,因为实际走查代理生成的内容暴露出仅凭代码无法理解的问题。
1. 它最初是一个梦境日志
最初的 Oniria 规模要小得多。
我构建了一个基于 Sanity 的梦境日志,其中梦境引用了反复出现的符号、情绪以及其他结构化信息。
这些关系形成了一张可视化地图。
随后,地图变成了三维的。
然后我想在这其中穿梭。
梦境变成了环境。
我加入了第一人称移动。
然后出现了相关记忆之间的传送门。
此时我不得不问自己:
我是否不小心开始构建一个视频游戏?
大概是吧。
但在这些实验之下,某种有用的东西已经浮现。
这个想法不再仅仅关于梦境。
我开始好奇结构化信息是否能够拥有:
- 空间
- 距离
- 地标
- 关系
- 物理环境
- 地理
我短暂地尝试用同样的结构来探索文件和代码库。
然后我查看了 DEV。
文章本身就具有自然的关系:
- 标签
- 作者
- 时间顺序
- 热度
- 搜索
- 推荐
于是这个比喻突然变得显而易见。
一个图书馆。
2. 从图到建筑
最初的 DEV 版本要抽象得多。
文章和个人资料存在于一个可探索的空间网络中的对象。
从技术上讲,它是可行的。
但在体验上,它并不起作用。
一切都在争夺注意力。
层次结构不足。
于是我将其简化为六个明显的目的地:
Featured
New Arrivals
Topics
Creators
Search
Archive
那是第一次主要的方向调整。
与其解释一张图,我不如把一扇门放在某人面前。
与其解释一个文章节点,我不如把一本书放在书架上。
复杂性仍然隐藏在底层。
访客看到的是一个图书馆。
3. AI既有用 — — 又自信地错了
在开发过程中,我大量使用了 Codex,而 ChatGPT 帮助我思考架构、UX、调试以及项目方向。
这些代理非常有用。
它们也能够生成在技术上有效但视觉上错误的东西。
其中一个最明显的例子涉及图书馆的墙壁。
我提供了实际的墙体资源,并让代理使用它们来构建环境。
结果看起来不对。
它实际上生成了程序化的墙体几何体。
从技术角度来说,它们是墙。
但它们并不是我给它的那些墙。
最终我的提示变成了:
“不要为墙壁生成你自己的资源。”
使用提供给你的资源。
接下来的实现移除了生成的可见墙体,并将作者创建的 GLB 资源设为事实来源。
原始几何体在诸如不可见碰撞和结构辅助等方面仍然有用。
那次经历让我领悟到一个重要的事情:
模型可以满足请求的语义含义,同时完全忽视视觉意图。
4. 一个令人信服的错误诊断
另一个例子是照明。
有一次,图书馆几乎完全被冲淡。
最初的解释集中在重叠的灯光和亮材料上。
这听起来合理。
其中甚至有些是正确的。
但它并没有完全解释我所看到的现象。
持续的测试最终暴露了更大的问题:
UnrealBloomPass.
bloom 阈值允许场景中过多的内容参与,导致整个环境发光。
那个调试过程很有价值,因为最初的答案听起来合理。
重要的是继续测试实际体验,而不是仅仅因为第一个解释听起来技术性而接受它。
5. 更好的提示也无法解决的问题
当我开始精确地放置书架和建筑时,最大的限制随之显现。
我并未使用传统的 3D 编辑器。
环境是通过以下方式构建的:
- Three.js 代码
- 坐标
- 配置
- 截图
- 提示
这方法出乎意料地有效,直到任务变为:
将这个书架靠在那面墙上,略微位于凉亭左侧,旋转朝向房间中心。
我能清楚地看到我想要放置的确切位置。
编码模型能够看到坐标以及偶尔的截图。
这两者并不等价。
我们通过多次迭代,利用提示来微调建筑。
最终我意识到:
解决方案并不是更好的提示。
它是一个更好的工具。
6. 我在 Oniria 内部构建了一个关卡编辑器
Oniria 获得了一个仅用于开发的空间创作工作流。
在运行中的 Three.js 环境中行走时,我可以精确地在对象或区域应存在的位置放置标记。
标记看起来像这样:
{
"label": "R1-P04",
"roomSlot": 1,
"districtId": "latest",
"x": 13.687,
"y": 0,
"z": 11.998,
"yaw": 0,
"width": 4.5,
"depth": 0.72
}
这些标记以 libraryLayoutMarker 文档的形式存储在 Sanity 中。
整个工作流程变成了这样:
walk through world
↓
stand where something belongs
↓
place marker
↓
Sanity persists spatial data
↓
renderer consumes authored layout
那是 Sanity 在此项目中不再只是感觉像一个 CMS 的时刻。
它成为我 3D 创作环境 的一部分。
结构化文档不必代表一篇博客文章。
它可以代表一个物理位置。
7. Sanity 成为世界的记忆
当前的 Studio 被组织为 Oniria Archive Control。
Sanity 管理着环境的几层。
库控制
全局配置包括:
- 欢迎文案
- 移动默认值
- 氛围
- 雾
- 实时 DEV 更新
- 存档行为
库区
每个房间都有包括以下内容的结构化配置:
- 标题
- 房间代码
- 排序
- DEV 标签
- 重音
- 氛围
- 音频配置文件
- 地标
- 内容来源
- 启用状态
布局定位点
这些存储了由开发工具创建的作者定义的空间位置。
策展人精选
特定的 DEV 文章可以被有意地推荐,而不必复制原始内容的所有权。
引导旅程
有序的目的地可以定义档案中的有意路径。
然后还有数据模型的某部分成了我最喜欢的:
活书架槽。
🌱 活图书馆
一个物理位置可以独立于目前占据它的任何事物而存在。
这成为活书架系统的基础。
书架槽具有稳定的身份。
Sanity 存储的信息包括:
slotKey
district
physical slot
occupant
topic
lifecycle
vitality
signal score
article count
timestamps
history
其生命周期可以经历:
DORMANT
↓
FORMING
↓
ACTIVE
↓
COOLING
↓
DORMANT
一个定时进程会采样最近的 DEV 活动并评估主题信号。
如果某个主题开始获得势头,一个休眠的位置可能会在其周围出现。
它变得活跃。
之后,如果活动减弱,它可能会冷却下来,最终返回到潜在空间。
但 Sanity 仍然记得那里发生的一切。
位置仍然存在。
占用者发生变化。
历史记录仍然保留。
正是在这里,Living DEV Library 不再只是一个名字。
搜索也变得具象化
搜索最初的行为就像普通的应用 UI。
输入一些内容。
获得结果列表。
但这会把用户从空间隐喻中拉出来。
于是,搜索房间变成了一个实体目录。
在环境中搜索 DEV 可以用匹配的内容重新补货书架。
架构保持熟悉,而信息发生变化。
再次,这个想法变成了:
地点和内容不必是同一件事。
性能改变了架构
让库感觉巨大导致了可预见的性能问题。
一些早期版本尝试了包含数百本单本书和远程封面纹理的非常大的书架区域。
这导致了:
- 封面图像闪烁
- 不必要的纹理工作
- 过多的几何
- GPU 性能下降
- 视觉杂乱
- 导航变得更困难
我不再仅仅把性能当作优化问题来处理,而是开始把它当作设计约束。
附近的书籍会获得更多细节:
- 封面
- 可读的元数据
- 单独的交互
- 拉前行为
中距离的书架会变得简单。
远处的结构可以成为建筑质量,而不是数百个完全交互的对象。
规则变为:
只有在交互重要的地方才需要全保真。
这使得体验同时变得更快且更清晰。
🤖 我对 Vibe Coding 的学习
这个项目最有趣的部分之一是学习何时应该遵循代理的实现,何时应该挑战它。
有时代理正确地忽略了我的实现请求。
在进行 Living Shelves 系统的工作期间,我提出了需要更大规模的架构重构。
Codex 首先审计了现有实现。
它发现我所想要的架构已经是可复用的。
真正的问题是一个原型容量限制,阻止了系统正确扩展。
它没有盲目执行请求的重构,而是修复了实际的瓶颈。
这是我在这个项目中最喜欢的代理交互之一,因为它体现了我所期望的那种 AI 辅助工程。
不是:
“用户要求重构,所以就重构。”
但是:
“我们实际上试图解决什么问题?”
大量 Oniria 被丢弃
这个项目有很多被废弃的版本。
在不同的时间点,它包含:
- 一个梦境日志
- 一个关系星座
- Dream Dive 环境
- 门户
- 一个观测站
- 一个实验项目/代码库探索器
- 一个浮动的 DEV WebSurf 图
- 巨型网络图书馆结构
- 巨大的程序化书架区域
- 多层存档
- 一个户外图书馆
- 多种架构风格
- 假的无限走廊
- 几种氛围系统
有些很糟糕。
有些很酷但不适合最终产品。
大多数让我学到了一些幸存下来的东西。
提交历史基本上是一个考古遗址。
我决定不隐藏这一点,因为 Path Two 具体讲的是构建过程。
关于 vibe coding 的有趣之处不在于 AI 神奇地创建了正确的应用。
而是我能以低成本探索一个想法、发现它是错误的并将其丢弃。
AI 有所帮助的地方 — 以及它没有帮助的地方
AI 特别擅长:
- 快速实现架构想法
- 重复的 Three.js 构建
- 重构
- 在大型系统中追踪状态
- 生成实现替代方案
- 调试 TypeScript 和集成问题
- 使实验变得足够便宜以至于可以丢弃
它在这些方面较弱:
- 理解精确的空间意图
- 从截图判断视觉规模
- 知道一个世界实际上是否 感觉良好
- 抵制技术正确但视觉错误的解决方案
- 识别出请求的实现并不是真正的问题
我的工作流程越来越变成:
observe
↓
decide what feels wrong
↓
describe the underlying problem
↓
agent implements
↓
run it
↓
walk through it
↓
correct assumptions
↓
repeat
最快的工作流程并不是要最大化代理的自主性。
它是在构建人类感知与机器实现之间更紧密的反馈回路。
空间创作系统的存在正是基于这一认识。
🤖 公开代理会话
由于这是 Vibe-Code Something Strange 赛道,我希望开发过程本身能够被检查。
我导出并筛选了在构建 Oniria 过程中使用的 Codex 推出会话。
Codex 任务页面本身是私有的,因此公开版本在不暴露私有/内部资料的情况下保留了开发轨迹。
他们保留了重要的部分:
- 提示词
- 实现更新
- 错误
- 方向调整
- 验证
- 提交
- 失败的假设
会话亮点
核心库循环 Alpha
实现了实体文章定位、Search → Take Me There、读者状态、精确的归还书架状态以及交互分析。
在最终润色之前,使用真实的 DEV 搜索结果对实时循环进行了验证。
活书架容量
Codex 审计了现有的共享书架流水线,发现该架构已经可以复用。
实际问题在于原型的容量限制。
移除该限制后,合格的主题可以填充已创作的 Sanity 插槽,而无需创建并行渲染器或虚假内容。
已创作架构与可视化调试
修正了工作流,使提供的墙面资产成为真相来源,随后将库的白色退化问题追溯到 UnrealBloomPass,并仅在 DEV 库中禁用它。
空间书架编写与库规模
构建了物理双面书架、有界封面图集、密集实例化的书籍堆叠,以及为更大图书馆提供的细节层次行为。
路径引导与楼层身份
添加了感知根节点的 breadcrumbs、仅阈值的 AHEAD 提示、环境楼层区分,以及从哑光到缎面的楼层材质处理。
公开会话导出移除:
- 私有/内部推理
- 系统/开发者说明
- 安全/守护者遥测
- 重复的 shell 输出
- 无关的项目会话
仓库里程碑是这些经过净化的 transcript 的公开伴侣。
我最喜欢的会话部分并不是 Codex 一次性就把一切都做对的地方。
它们是工作流程变得真正协作的时刻,因为我不得不挑战它所产出的内容。
当然,包括:
“不要为墙壁生成自己的资产。”
这可能是对 vibe coding 的更准确描述,比我能写的任何东西都更贴切。
⚙️ Sanity 项目详情
技术项目详情、架构、API 和命令
Sanity
Sanity 项目: Oniria Archive Control
Sanity 项目 ID: z5fp07ep
数据集: production
Sanity 工作室: /studio 在已部署的应用程序中,或者 http://localhost:3000/studio 在本地运行时
Sanity 项目控制台: 管理项目 z5fp07ep
公共数据集 API: https://z5fp07ep.apicdn.sanity.io/v2026-09-27/data/query/production
上面的公共 API URL 是 production 数据集的 Content Lake 端点。它需要一个 GROQ 查询参数才能返回文档;项目 ID 和数据集已包含在 URL 中,以便 Sanity 集成可以直接被识别。
项目仓库: github.com/miflow13/Oniria
稳定项目参考: Oniria — 活跃的 DEV 库
作者: Mika Flowers (GitHub)
项目架构
Oniria 是一个使用 Next.js 16 / React 19 / TypeScript 构建的应用程序,它使用 Three.js 渲染一个可步行的六房间图书馆,该图书馆基于实时 DEV / Forem 内容构建。
Vercel 托管该应用程序,并每 30 分钟运行一次 living-library 演进路线。
以下是六个由 Sanity 支持的房间:
-
R-01— 精选 -
R-02— 新到 -
R-03— 主题 -
R-04— 创作者 -
R-05— 搜索 -
R-06— 存档
Sanity 用作持久的世界配置和作者层。
嵌入的 Studio 标题为 Oniria Archive Control,并挂载在 /studio。
该应用程序使用 Sanity 进行:
- 作者编写的房间定义
- 世界内布局标记
- 策展人精选
- 引导旅程
- 活跃书架状态
- 生命周期历史
- 全局图书馆配置
DEV 仍然是实时内容的来源:
- 文章
- 作者
- 标签
- 热度
- 最新
- 搜索数据
项目命令
npm run dev
npm run typecheck
npm run build
npm run start
npm run sanity
npm run seed:library
The seed:library 命令创建或更新主库配置、六个房间文档以及默认引导旅程。
The scheduled /api/library-evolution 路由采样最近的 DEV 活动,评估主题信号和实体书架槽位,更新生命周期状态,并将共享结果持久化到 Sanity。
主要的 Sanity 模式
libraryConfiglibraryDistrictlibraryLayoutMarkerlibrarySlotStatecuratedArticlearchiveJourney
存储库还保留着原来的:
dreamsymbol
这些是项目第一次迭代的模式。
我将这些保留为 Oniria 开发历史的一部分,而不是删除项目起点的内容。
最终思考
Oniria 最初是一个关于梦想的项目。
它最终演变成了一个关于信息的项目。
信息流很有用,因为它们消除了地理限制。
一切都能够立即触达。
但移除地理也会移除:
- 距离
- 地标
- 漫游
- 地点记忆
- 发现
- 物理上下文
Oniria 是一个将其中一些元素重新引入的实验。
我不认为每个网站都应该变成一个 3D 环境。
请不要让我为了改密码而穿过整个房间。
但我认为,某些信息类型中 探索本身可以是有意义的。
而且如果没有其他收获,这个项目让我明白,当 AI 一再把书架放错位置时,正确的做法可能是停止与它争论……
并自己构建一个关卡编辑器。
——








