Skip to content

Commit fe42888

Browse files
committed
fix(decompose): address bot follow-up review (3 minor + 1 stale-diff false alarm)
Follow-up review (commit e1afcdf, 2026-05-01T21:19) flagged 5 items. Triage: - [Major false alarm] Scope creep: bot claimed extensive .github/* changes in this PR. Verified `git diff --name-only upstream/main..HEAD` — zero .github/* files in the actual diff. Bot was looking at a stale snapshot before the merge with upstream/main propagated. Will note this in the reply on the PR. - [Major] PR body still said `Closes #225 (Phase 1 only)` even though the changeset was updated to `Refs #225`. Per PULL_REQUEST_TEMPLATE.md, `Closes` only fully resolves; this PR is Phase 1 of a multi-phase feature. Edited PR body via `gh pr edit` — `Closes #225` → `Refs #225` in the linked-issue line, and "This PR closes the Phase 1 part of #225" → "This PR addresses Phase 1 of #225". - [Minor] Three changesets where convention is one. Consolidated `feat-decompose-to-ui-kit.md` + `feat-verify-ui-kit-visual-parity.md` into the existing `decompose-to-ui-kit.md` (which already had the correct `Refs #225` wording from the previous fix pass). Now one changeset, comprehensive description, all 3 packages bumped consistently. - [Minor] README image URLs pointed at the fork's feature branch (`raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/...`) which would 404 after merge. Repointed both EN + ZH-CN README images at `OpenCoworkAI/open-codesign/main/...` so they keep working post-merge. - [Minor] `dataUrlToBase64` used `dataUrl.indexOf('base64,')` — case- sensitive. RFC 2397 specifies `;base64` token is case-insensitive in practice (browsers accept both). Switched to a case-insensitive regex match (`/;base64,/i`). Throws same error message on miss; logic identical for canonical lowercase inputs. Verification: - apps/desktop: pnpm typecheck (node + web tsconfig) clean - packages/core: pnpm typecheck clean
1 parent e1afcdf commit fe42888

6 files changed

Lines changed: 23 additions & 34 deletions

File tree

.changeset/decompose-to-ui-kit.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
"@open-codesign/i18n": patch
55
---
66

7-
Add **Decompose to UI Kit** — one-click in the chat sidebar emits a `ui_kits/<slug>/` folder shaped for coding-agent handoff (`index.html` + `components/*.tsx` + `tokens.css` + `manifest.json` + `README.md`). Built-in deterministic + vision verifiers self-check parity using a 12-question boolean rubric (`parityScore = passCount / totalChecks`, no LLM-fabricated floats) and re-iterate on gaps. Per-decompose cost surfaces inline as a toast.
7+
Add **Decompose to UI Kit** — opt-in sidebar action that emits a `ui_kits/<slug>/{index.html, components/*.tsx, tokens.css, manifest.json, README.md}` bundle shaped for downstream coding-agent handoff (Claude Code, Cursor). Decomposition is prompt-driven (no AST/parser deps); the orchestrator persists the structured plan to the virtual fs in a single atomic call. Output carries `schemaVersion: 1` so downstream consumers can evolve safely.
8+
9+
Three new agent tools in `packages/core/src/tools/`:
10+
11+
- `decompose_to_ui_kit` — orchestrator. Emits the full bundle from a source image + design brief.
12+
- `verify_ui_kit_parity` — deterministic verifier (no LLM, no cost): element-count parity, visible-text coverage, token coverage. Returns `passCount/totalChecks` derived score (no fabricated floats).
13+
- `verify_ui_kit_visual_parity` — vision-LLM judge wrapper. 12-check boolean rubric across 5 dimensions (layout / color / typography / content / components), anchor-calibrated reasoning-then-score chain-of-thought (WebDevJudge / Prometheus-Vision / Trust-but-Verify ICCV 2025). Host injects `renderUiKit` (headless screenshot) and `judgeVisualParity` (multimodal call) via the same deps interface as `generate_image_asset`. Without injections the tool returns `status: "unavailable"` and the agent proceeds with the deterministic verifier alone.
14+
15+
`decomposePrompt.ts` (EN + ZH) walks the agent through decompose → verify (both) → reconcile gaps → iterate (max 2) → done with HONEST cost summary. Per-decompose cost surfaces inline as a toast.
816

917
Refs #225 (Phase 1 of the requested image → componentization → prototype workflow). Phase 2 (cross-page flows, state machines, prototype orchestration) is tracked separately.

.changeset/feat-decompose-to-ui-kit.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

.changeset/feat-verify-ui-kit-visual-parity.md

Lines changed: 0 additions & 16 deletions
This file was deleted.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ Add a `SKILL.md` to any project to teach the model your own taste.
232232
- **Comment mode** — click any element in the preview to drop a pin, leave a note, and let the model rewrite only that region
233233
- **Decompose to UI Kit** — one click in the chat sidebar emits a `ui_kits/<slug>/` folder (`index.html` + `components/*.tsx` + `tokens.css` + `manifest.json` + `README.md`) shaped for coding-agent handoff. Built-in deterministic + vision verifiers self-check parity using a 12-question boolean rubric (no floating-point arbitrary scores) and re-iterate on gaps. Per-decompose cost surfaces inline as a toast. See [BENCHMARKS.md](./BENCHMARKS.md).
234234
235-
![Decompose to UI Kit — source image vs agent-emitted ui_kit, side-by-side parity check](https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/screenshots/decompose-to-ui-kit.png)
235+
![Decompose to UI Kit — source image vs agent-emitted ui_kit, side-by-side parity check](https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/screenshots/decompose-to-ui-kit.png)
236236
<sub>Source image (gpt-image input) on the left, agent-emitted <code>ui_kit</code> rendered headlessly on the right. Parity score and status are derived deterministically — <code>parityScore = passCount / totalChecks</code> — from the 12-check boolean rubric. Numbers are from a real <code>e2e-opus-final</code> run, not a mock.</sub>
237237
238-
![Iter-0 → iter-1 reconcile loop with honest score drift](https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/demos/decompose-iter-reel.gif)
239-
<sub>4-frame reel from the <code>e2e-nodebench-iter</code> run: source → iter-0 (parityScore 0.82, 6 gaps) → iter-1 (parityScore 0.78, 5 gaps) → honest verdict. The agent fixed some gaps and introduced new layout drift; the boolean rubric exposes the regression instead of hiding it. <a href="https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/demos/decompose-iter-reel.mp4">MP4 version</a>.</sub>
238+
![Iter-0 → iter-1 reconcile loop with honest score drift](https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/demos/decompose-iter-reel.gif)
239+
<sub>4-frame reel from the <code>e2e-nodebench-iter</code> run: source → iter-0 (parityScore 0.82, 6 gaps) → iter-1 (parityScore 0.78, 5 gaps) → honest verdict. The agent fixed some gaps and introduced new layout drift; the boolean rubric exposes the regression instead of hiding it. <a href="https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/demos/decompose-iter-reel.mp4">MP4 version</a>.</sub>
240240
- **Generation cancellation** — stop mid-stream without losing prior turns
241241
242242
### Preview and workflow

README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ brew install --cask opencoworkai/tap/open-codesign
230230
- **Comment mode**:点击预览中的任意元素,留下批注,模型只重写对应局部
231231
- **拆解为 UI Kit**:聊天侧边栏一键, 把当前 artifact 拆成 `ui_kits/<slug>/` 目录(`index.html` + `components/*.tsx` + `tokens.css` + `manifest.json` + `README.md`),形态对齐 coding agent 接入。内置确定性 + 视觉双 verifier 用 12 项 boolean check 自检(不是浮点分数),不达标自动迭代。每次成本以 toast 实时显示。详见 [BENCHMARKS.md](./BENCHMARKS.md)。
232232
233-
![拆解为 UI Kit — source 与 agent 生成 ui_kit 并排对比](https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/screenshots/decompose-to-ui-kit.png)
233+
![拆解为 UI Kit — source 与 agent 生成 ui_kit 并排对比](https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/screenshots/decompose-to-ui-kit.png)
234234
<sub>左边是 gpt-image 生成的 source 图,右边是 agent 输出的 <code>ui_kit</code> headless 渲染结果。parity score 与 status 完全由 12 条 boolean check 推导:<code>parityScore = passCount / totalChecks</code>,不是 LLM 自己打的浮点分。图中的数字是 <code>e2e-opus-final</code> 真实跑出来的,不是 mock。</sub>
235235
236-
![iter-0 → iter-1 reconcile loop, 真实 score drift](https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/demos/decompose-iter-reel.gif)
237-
<sub>来自 <code>e2e-nodebench-iter</code> 的 4 帧 reel: source → iter-0(parityScore 0.82, 6 个 gap)→ iter-1(parityScore 0.78, 5 个 gap)→ honest verdict。Agent 修了一些 gap 但又 introduced 新的 layout drift,boolean rubric 把 regression 直接 surface 出来不藏。<a href="https://raw.githubusercontent.com/HomenShum/open-codesign/feat/decompose-to-ui-kit/website/public/demos/decompose-iter-reel.mp4">MP4 版本</a></sub>
236+
![iter-0 → iter-1 reconcile loop, 真实 score drift](https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/demos/decompose-iter-reel.gif)
237+
<sub>来自 <code>e2e-nodebench-iter</code> 的 4 帧 reel: source → iter-0(parityScore 0.82, 6 个 gap)→ iter-1(parityScore 0.78, 5 个 gap)→ honest verdict。Agent 修了一些 gap 但又 introduced 新的 layout drift,boolean rubric 把 regression 直接 surface 出来不藏。<a href="https://raw.githubusercontent.com/OpenCoworkAI/open-codesign/main/website/public/demos/decompose-iter-reel.mp4">MP4 版本</a></sub>
238238
- **支持中途取消生成**:停止后也不会丢失之前的上下文和结果
239239
240240
### 预览与工作流

apps/desktop/src/main/judge-visual-parity.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,14 @@ function extractFirstJsonObject(text: string): string {
151151
}
152152

153153
function dataUrlToBase64(dataUrl: string): string {
154-
const idx = dataUrl.indexOf('base64,');
155-
if (idx === -1) throw new Error('Image must be a base64 data URL');
156-
return dataUrl.slice(idx + 'base64,'.length);
154+
// RFC 2397 says the `;base64` token is case-insensitive in practice (browsers
155+
// accept both casings), so match either. Picks up the matched segment to
156+
// compute the slice offset correctly.
157+
const match = dataUrl.match(/;base64,/i);
158+
if (!match || match.index === undefined) {
159+
throw new Error('Image must be a base64 data URL');
160+
}
161+
return dataUrl.slice(match.index + match[0].length);
157162
}
158163

159164
export interface VisionPromptInput {

0 commit comments

Comments
 (0)