Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8d62cfd
feat: align HomeVox product flow with Penpot parity
Jul 24, 2026
c832928
fix: route Issue 19 product states by stable wall id
Jul 24, 2026
7723a7b
test: restore Issue 19 production lifecycle evidence
Jul 24, 2026
695e042
fix: close invalid 3d workspace and verify linked edits
Jul 24, 2026
50f7651
fix: fail closed without wasm 3d geometry
Jul 24, 2026
76f6272
fix: close Issue 19 parity review gaps
Jul 24, 2026
b438898
fix: fail closed on degenerate canonical walls
Jul 25, 2026
a91f597
fix: preserve wasm openings and show completed steps
Jul 25, 2026
87ad3cf
fix: close issue 19 acceptance gaps
Jul 25, 2026
2bfc3c4
fix: harden current 3d product flow
Jul 25, 2026
90d1909
fix: make linked 3D review visible and current
Jul 25, 2026
0ed336b
fix: close 3D frame and export races
Jul 25, 2026
01df273
feat: align HomeVox product flow with Penpot
Jul 25, 2026
968ee22
fix: resolve issue 19 product visual parity
Jul 25, 2026
7fabbb7
fix: preserve unknown floorplan scale
Jul 25, 2026
cc32e2c
build: add LazyCat production packaging
Jul 25, 2026
023ba84
feat: add reliable floorplan crop flow
Jul 25, 2026
30ae3d5
fix: clarify floorplan judging feedback
Jul 26, 2026
e49adc9
fix: bind vision output to source pixels
Jul 26, 2026
7b1f44a
fix: trust decoded floorplan dimensions
Jul 26, 2026
e3885c4
feat: secure resumable recognition snapshots
Jul 26, 2026
d53b949
fix: retire legacy snapshot capabilities
Jul 26, 2026
12ae592
fix: render visible walls from wasm geometry
Aug 8, 2026
cf82156
build: reuse installed pinned wasm toolchain
Aug 8, 2026
3061055
ci: run full validation for pull requests
Aug 8, 2026
7c13f1d
ci: avoid duplicate feature branch runs
Aug 8, 2026
39fedbc
fix: address persistence and editing QA findings
Aug 8, 2026
e784431
fix: close remaining issue 19 QA gaps
Aug 8, 2026
bd70836
test: resolve real wasm regression fixture
Aug 8, 2026
8eeb554
test: statically load wasm regression fixture
Aug 8, 2026
6a46c1e
test: keep voxel bounds regression portable
Aug 8, 2026
50c5587
fix: address remaining issue 19 QA gaps
Aug 9, 2026
6099001
fix: execute recovery regression and clear stale retry
Aug 9, 2026
414778e
fix: close PR 21 QA regressions
Aug 9, 2026
8a038df
test: serialize shared PostgreSQL regressions
Aug 9, 2026
d5f3242
test: construct valid legacy recovery fixture
Aug 9, 2026
53004a8
chore: version test LPK uniquely
Aug 9, 2026
ae3ca98
fix: make legacy recovery key optional
Aug 10, 2026
2ac26f8
fix: expose temporary AI runtime environment
Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.gitignore
.github
.hermes

frontend/node_modules
frontend/dist
frontend/test-results
frontend/playwright-report

backend/target
wasm/target
wasm/pkg

dist
*.lpk
.env
.env.*
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: HomeVox CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: homevox-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
backend:
name: Backend tests
runs-on: ubuntu-24.04
services:
postgres:
image: postgres:17-alpine
ports:
- 5432:5432
env:
POSTGRES_DB: homevox_test
POSTGRES_USER: homevox
POSTGRES_PASSWORD: homevox
options: >-
--health-cmd "pg_isready -U homevox -d homevox_test"
--health-interval 5s
--health-timeout 5s
--health-retries 10
defaults:
run:
working-directory: backend
env:
HOMEVOX_TEST_DATABASE_URL: postgres://homevox:homevox@localhost:5432/homevox_test?sslmode=disable
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
# PostgreSQL regressions in internal/api and internal/db both exercise schema
# initialization against this single service. Keep packages serial so their
# destructive legacy-schema fixtures cannot race each other.
- run: go test -p 1 ./...

frontend-and-wasm:
name: Frontend, WASM, and production build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm --prefix frontend ci
- run: ./scripts/bootstrap-wasm.sh
- run: cargo +1.96.1 test --manifest-path wasm/Cargo.toml
- run: npm --prefix frontend run lint
- run: npm --prefix frontend test
- run: npm --prefix frontend run build

production-browser-e2e:
name: Production browser persistence E2E
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm --prefix frontend ci
- run: HOMEVOX_E2E_ARTIFACT_DIR="$RUNNER_TEMP/homevox-e2e-artifacts" HOMEVOX_KEEP_E2E_ARTIFACTS=1 npm --prefix frontend run test:e2e
- if: always()
uses: actions/upload-artifact@v4
with:
name: production-browser-e2e-${{ github.sha }}
path: ${{ runner.temp }}/homevox-e2e-artifacts
if-no-files-found: warn
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ Thumbs.db

wasm/target/
wasm/pkg/

# LazyCat production release output
dist/lpk/
*.lpk
.lzc-cli-cache/
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@

### Added

- 2D 校正工具栏可在生成 3D 前直接创建服务器端识别快照,后续墙体/门窗调整以 revision 更新同一个项目且不会重新调用候选分析或 Vision parse。项目 UUID 不作为访问凭证:创建时生成 256-bit capability,数据库只存 SHA-256;detail/source/update 必须携带专用 header,并全部使用 `Cache-Control: no-store`。继续编辑链接只在 URL fragment 中交接 capability,应用启动后立即清除 fragment;source-image URL 必须精确绑定当前项目同源 API;全局项目列表在没有 owner 身份边界时关闭。
- capability schema 升级对旧项目采取显式 fail-closed:保留旧记录与对象,以随机不可兑换的 64-hex digest 替换缺失凭据,并把 `capability_hash` 固化为 `NOT NULL` 且受格式约束;不会让历史 UUID 退化为访问凭据。durable wall/opening ID 同时统一为前后端均可重载的 `[A-Za-z0-9_-]+`。
- 选中墙体后可直接输入起点/终点四个 source-pixel 坐标;数值编辑保留共享端点、进入 Undo/Redo,并与拖拽共用 effective source 边界和 opening 校验。前端拒绝越界、非有限、退化或破坏开口的编辑,后端项目 create/update 同样拒绝超出 durable image grid 的墙体和房间边界。
- 修复真实 Vision Provider 默认按内部预处理尺寸回报 `metadata.image_width/image_height`,导致有效裁切被错误拒绝的问题:解析请求显式提供后端已解码的原图精确尺寸与坐标网格,但 Provider 回报的 metadata 仅视为不可信的预处理信息;durable metadata 由上传字节的真实宽高覆盖,任何房间/墙体坐标超出 decoded image bounds 仍失败关闭。真实懒猫浏览器以 `4701 × 4501` 裁切验证 `/api/floorplans/parse` 返回 200,并进入同尺寸、可编辑墙体覆盖可见的 2D 校正页。
- 裁切确认后的真实 Provider 等待阶段现在显示动态“正在判断当前裁切区域…”状态并禁用重复提交;若裁切图仍缺少完整、无遮挡的墙体边界,失败说明会明确要求继续调整或更换原始无遮挡图,不再重复泛化提示“裁切到单个户型”。
- Issue #19 milestone C:导入链路改为自动候选判断;single 候选自动裁切后解析,composite/uncertain 和分析失败进入可继续的手动裁切。有效裁切图是解析、2D 底图和项目 source-image 的唯一来源,原图不持久化。生产 Playwright/fake-vision 合同验证受控提示词返回的真实矩形、裁切后的尺寸/内容及 parse 失败后的重试保留状态。
- Issue #19 parity 的权威原型更新为 File `7622c4ac-2f6b-802b-8008-5f15321d47e8` / Page `7622c4ac-2f6b-802b-8008-5f15321d47e9`,版本 **HomeVox · 主流程 · 自动判断与裁切 · 草稿 04 · 视觉验收**,共 8 个 Board。
- 图片在完整解码前限制单边 `16384` 像素和总计 `100,000,000` 像素,阻断压缩图片炸弹;项目创建与更新均以真实 effective source 的 MIME、字节数和像素宽高绑定 canonical document,更新时会回读不可变对象验证。

- Add repository-owned LazyCat production LPK configuration: local development remains a direct `0.0.0.0:18088` process reached through the development machine's port-prefix URL, while the production LPK runs HomeVox on container port `18088`, persists PostgreSQL and MinIO under `/lzcapp/var`, and keeps every `.lpk` artifact ignored.
- 修复真实高干扰营销户型图验收发现的比例尺合同矛盾:`scale.pixel_to_unit` 现在是**必填的有限 number 或显式 null**;比例未知时只能持久化为 `{ "unit": "px", "pixel_to_unit": null }`,绝不猜测物理尺寸。严格 JSON、项目保存/重载、前端 guard 与 fake vision 合同同步收紧,缺失字段、字符串 unknown、null confidence/图像尺寸、重复键和尾随 JSON 仍失败关闭。
- AI 识别失败现在区分服务不可用、模型输出格式不完整和图片中无可靠户型拓扑;复杂营销复合图会明确提示裁切到单个户型或上传更清晰的平面图,不再误报为网络问题。

- 修复 Issue #19 独立视觉验收发现的产品问题:移除普通用户界面的内部标识和工程术语,统一以「待确认 / 尚未测量 / 尚未识别」表达未确认信息;真实底层状态、选择、历史、导出与失败关闭链路不变。
- 恢复 Tailwind spacing utilities 的正常层叠,并将 01、03、04、05 的 1440 × 960 live Penpot 卡片、网格、画布和边距写入 production DOM geometry gate;2D SVG 现在自适应居中,消除右侧黑色裁切和联动页下方无意义空白。
- 3D 完成页现在只在可见空间预览真正准备好后显示完成态;生产测试同时约束客户可见文案、选择、真实像素、开洞、异步更新和过期导出拒绝。

- 完成 Issue #19 的 Penpot 产品化闭环:直接通过已配置的 Streamable HTTP Penpot MCP 回读当前 live file/page 与 8 个 1440 × 960 Board,并将 232px 侧栏、72px 顶栏、Inter 字体层级、颜色、圆角、工作区与 unknown 语义固化为前端产品设计合同和 production layout gate。
- 导入页选择真实图片后自动调用 `/api/floorplans/candidates`:清晰单户型自动裁切并进入 `/api/floorplans/parse`,复合图与不确定结果进入原像素裁切确认;任何分析失败都只降级为可操作的全图手动裁切,不伪造候选。
- 校正、生成 3D、联动和保存页面改为对应 Penpot 的产品工作区;保留真实 canonical、R3F/Rust-WASM、双向选择、Undo/Redo、保存/reload 和 stale-export fail-closed 链路,同时不在普通界面暴露原始 ID、WASM/端口/时序或 JSON 诊断信息。
- 为 Vite 8 开发模式补齐 React Refresh 浏览器 preamble;独立 5173 HMR 服务器可在不替换 18088 production preview 的情况下真实热更新 CSS 产品变更。

### Verification

- 生产 Playwright 在隔离 PostgreSQL + MinIO 中验证:2D 数值编辑后创建 revision 1 快照,独立 Chromium context 通过 capability fragment 恢复同一坐标,fragment 在请求前从地址栏清除,GET/source/PUT 均携带专用 header,候选/parse 请求计数不增加;继续编辑后保存 revision 2,服务重启后仍恢复同一 canonical 几何。
- 新增 `productDesign` Vitest 合同和 production Playwright shell geometry gate(232px/72px);固定 1440 × 960 screenshot、像素与交互门禁继续覆盖 01/03/04/05。

- 完成 Issue #17 的受控 Vision 合同闭环:生产 Go multipart parse API 以 OpenAI-compatible `/chat/completions` 多模态请求处理浏览器图片,并对空/非 JSON envelope、schema-invalid opening geometry、timeout、429 与 5xx fail-closed;上游错误正文不会回显给浏览器。
- AI 解析不再把缺少 `wallId`、局部 `position` 或 `width` 的 opening 推断为已确认结构;这些候选直接拒绝,避免最近墙体或预览默认值污染 durable document。
- AI canonical 输出现在要求完整且唯一的 JSON object:拒绝未知字段、重复键、缺失或部分嵌套字段、`null`、错误 JSON 类型、legacy opening 字段及尾随第二个 JSON 值;parse 路径不再生成 ID、kind、source 或空集合。
Expand Down
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@ HomeVox 采用混合技术路线:**Go 管业务 API,Rust 管体素/几何核

- ✅ Go 单进程固定监听 `0.0.0.0:18088`,同源提供 `/api/*` 与 `frontend/dist`,并支持 SPA fallback
- ✅ 户型图上传与 OpenAI-compatible AI 解析接口已落地;正向解析运行需要配置 `AI_API_KEY`、`AI_BASE_URL`、`AI_MODEL`
- ✅ 2D 户型校正编辑器支持墙体选择、共享端点拖拽、Undo/Redo 与底图显隐
- ✅ 自动判断与裁切:新文件会先请求候选区域;单一候选自动生成裁切后的 effective source 并解析,复合/不确定候选进入可调整裁切。分析的传输、schema 或内容失败会安全回到全图手动裁切;裁切后解析失败会保留原图、候选和裁切框以便重试。
- ✅ 持久化边界:只保存已确认并实际解析的 effective source;原图仅保留在当前浏览器会话中用于重新裁切,不会上传为项目 source image。
- ✅ 2D 户型校正编辑器支持墙体选择、共享端点拖拽、起点/终点 source-pixel 数值输入、Undo/Redo 与底图显隐;越出 effective source、退化或破坏门窗开口的编辑不会提交。
- ✅ Issue #13:生产前端构建会从 `wasm/` 源码执行 `wasm-pack build --target web`,由受控 17³(4,913 voxel)标量场调用 Rust Marching Cubes,并在 R3F 中显示有限 position/normal 的真实 WASM 网格。
- ✅ Issue #17:浏览器上传链路的 AI 结果现在经受控 OpenAI-compatible Vision HTTP 合同和严格 canonical schema validation;未知字段、重复键、缺失/部分字段、null、错误类型和尾随 JSON 都会失败关闭,且不会补齐 AI 输出中的 ID、kind、source 或集合。
- ✅ 门窗 opening 由 wall-local `wallId`、`position`、`width` 与 `confirmed` 驱动真实 2D/3D 开洞;未确认的门高、窗高和窗台高仅为非持久化预览,绝不表述为建筑实测参数。
- ✅ WebGL 不可用时显示明确降级提示,不再留下空黑 3D 视口
- ✅ Issue #9:为当前可用 2D/3D 视图提供一次点击一次下载的 PNG 导出,支持空白/尺寸/序列化等失败闭环;3D 导出通过 R3F 渲染器即时渲染后抓取并规避对象 URL 过早回收
- ✅ Rust/WASM 几何核心已加入 Marching Cubes 功能验证
- ✅ Issue #11:项目可持久化保存、列表加载与修订保存已接入单端口 API。服务端在上传前分配 UUID,并以同一 UUID 写入 PostgreSQL 与 `projects/{uuid}/source-image` S3/MinIO 对象键;数据库写入失败会删除该对象。
- ✅ Issue #11:项目可持久化保存与修订保存已接入单端口 API。服务端在上传前分配 UUID,并以同一 UUID 写入 PostgreSQL 与 `projects/{uuid}/source-image` S3/MinIO 对象键;数据库写入失败会删除该对象。未建立用户身份体系前,全局项目列表关闭,避免枚举其他项目。
- ✅ AI 识别完成后可在 2D 阶段立即创建服务器端识别快照,后续显式“保存当前修改”沿用 revision conflict 保护且不会再次调用 Vision Provider。创建项目会生成 256-bit 随机 capability,数据库只存 SHA-256;detail/source/update 必须通过专用 header 携带明文 capability,并统一返回 `Cache-Control: no-store`。跨浏览器继续编辑链接只把 capability 放在 URL fragment,前端在发起项目请求前立即从地址栏清除;source-image URL 必须精确绑定当前项目同源 API。获得该链接的人可访问和修改项目,应按敏感凭据保管。
- ⚠️ 当前 capability 是单实例阶段的持有者读写授权,不等同于用户登录或租户隔离。正式多用户版本仍需 owner-bound identity,以及 capability 的轮换/吊销入口;在此之前不要把继续编辑链接发给不受信任的人。
- ⚠️ capability 上线前创建的旧项目没有可恢复的明文凭据。schema 升级会保留其数据库记录与对象,但以随机不可兑换 digest 明确退役访问,不会把旧 UUID 继续当作凭据;如需继续编辑,须从原始户型图重新创建受保护快照。
- ✅ 项目 API 仅在 PostgreSQL schema/ping 与 S3 bucket 均验证就绪时可用;`/api/config` 分别报告 `not_configured`、`incomplete_config`、`unavailable` 或 `ready` 状态。
- ✅ 已用隔离 PostgreSQL + MinIO 验证 create/get/list/source-image/update/stale-revision-409、重启后加载及未配置持久化时 project API 的 503;该验收使用 fixture 文档,不包含真实 AI 正向解析。
- ✅ 已用隔离 PostgreSQL + MinIO 验证 create、capability-gated get/source-image/update、stale-revision-409、独立浏览器恢复、重启后加载及未配置持久化时 project API 的 503;无 capability 的 UUID-only 请求失败关闭,全局 list 不可枚举。
- 3D 视口公开引擎状态、grid、三角形/顶点、调用耗时与输入/输出字节;单次主线程 WASM 调用预算为 50ms。加载、输入、输出或预算失败时明确回退到既有 wall-shell,2D 编辑、项目加载和 PNG 导出仍可用。

## 可复现 WASM 与浏览器验收
Expand All @@ -59,6 +64,31 @@ npm --prefix frontend run test:e2e

构建会生成忽略的 `wasm/pkg/` bindings 与 `frontend/dist/`;不要提交它们。生产浏览器验收由 Go 在 `0.0.0.0:18088` 提供 production assets,加载实际 `.wasm`(`application/wasm`),并使用受控 17³ fixture 验证 Rust 调用、有限几何、拖拽/Undo/Redo、3D PNG 下载及 reload 后重建。Playwright 首次使用前执行 `npm --prefix frontend exec playwright install chromium`。

## 本地开发与 LazyCat 生产发布

本地开发与 LazyCat 生产发布是两条独立链路。HomeVox 在开发机和生产容器内都固定监听 `0.0.0.0:18088`。

本地开发直接启动 Go 单端口服务,并通过开发机的 18088 端口前缀域名访问;不运行 `lzc-cli project deploy`,也不安装开发 LPK。若要跨浏览器保存识别快照,必须在进程环境或 ignored `backend/.env` 中提供可用的 `DATABASE_URL`、`S3_ENDPOINT`、`S3_BUCKET`、`S3_ACCESS_KEY_ID`、`S3_SECRET_ACCESS_KEY`,并以 `/api/config` 的 `databaseStatus: "ready"`、`s3Status: "ready"` 为准:

```bash
npm --prefix frontend ci
npm --prefix frontend run build
HOMEVOX_FRONTEND_DIR="$PWD/frontend/dist" go -C backend run ./cmd/server
# 浏览器访问开发机的 18088 端口前缀域名
```

仓库中的 `lzc-manifest.yml`、`lzc-build.yml`、`lzc-deploy-params.yml`、`package.yml`、`lzc-icon.png` 与 `images/Dockerfile` 只定义生产 LPK。生产发布使用 `lzc-cli project release -o <仓库外绝对路径>`,LPK 将入口转发至正式的 `homevox:18088` 容器,并包含 PostgreSQL 与 MinIO。任何 `.lpk` 都不得提交到仓库;LPK 的构建、验包、安装和发布需要单独授权。

部署包含 PostgreSQL 与 MinIO 持久服务,数据分别保存在 `/lzcapp/var/postgres` 和 `/lzcapp/var/minio`。数据库和对象存储使用 LazyCat `stable_secret` 生成实例内稳定密码;AI Provider 凭据不写入仓库或安装包,未配置时识别链路保持 fail-closed。

### d53 历史项目恢复密钥

`legacy_recovery_key` 是可选的 LazyCat `secret` 部署参数。运行期将其仅映射为
`HOMEVOX_LEGACY_RECOVERY_KEY`;不要把值写入仓库、镜像、普通环境文件或日志。
未配置时 HomeVox 正常启动,但一次性 d53 恢复端点保持 `403` fail-closed;该值仅
用于人工核验归属后调用恢复端点。需要恢复时可重新配置应用实例,不需要也不应将
密钥重新打包进 LPK。

## 许可

本项目采用 **GNU Affero General Public License v3.0 (AGPL-3.0)**。
Expand Down
14 changes: 14 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ The service uses one fixed public listener, `0.0.0.0:18088`:
- Missing assets and unknown API routes return `404`.

`HOMEVOX_FRONTEND_DIR` is required and must contain `index.html`; startup fails closed when the frontend build is missing. The backend deliberately ignores `HOMEVOX_LISTEN_ADDR` and never drifts from the fixed listener above.

## d53 legacy-project recovery

The historical `d53b949` schema replaced legacy `NULL` capabilities with random
64-hex digests. Those values cannot be distinguished automatically from a valid
bearer digest. Do **not** bulk-clear `capability_hash`.

After out-of-band ownership verification, an operator must add exactly one
`legacy_project_recovery_authorizations` row containing the project ID, the
currently observed retired digest, operator identity, and case reference. The
existing operator-key-gated recovery endpoint can then issue a replacement
capability; it writes `legacy_project_recovery_audit` and cannot be repeated.
Rows without that authorization (including ordinary bearer projects) remain
untouched.
Loading
Loading