Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
3e66cf9
feat(screen-plan): follow the final design page; audit treats headles…
Oct 22, 2026
ec8c03c
chore(plugin): rebuild dist
Oct 22, 2026
3f61f24
fix(export): oversized responseFields selection no longer blocks comm…
Oct 22, 2026
1722d5c
fix: flag excluded node pages, name device-only frames by section, wi…
Oct 22, 2026
3ff563b
fix(assets): decode HTML entities and drop parentheses in layer names…
Oct 22, 2026
604813c
feat(project-plan): staged units, shared change requests, API wiring …
Oct 22, 2026
ccc67ec
fix(export): oversized metadata under delivery=resource reports envel…
Oct 22, 2026
b343e6c
feat(search): warn when a name exists on both an excluded and a norma…
Oct 22, 2026
839ae2c
fix(export): envelope spill also covers fields kept inline when nothi…
Oct 22, 2026
2accfa6
style(export): clippy sort_by_key in envelope field sizing
Oct 22, 2026
33d0f9a
fix(screen-plan): name the real reason when a navigationTargets route…
Oct 22, 2026
feeaaa0
test(export): cover feedback 27 resource metadata delivery
Oct 23, 2026
91b08ca
fix(export): report feedback 28 field sizes and retained output bytes
Oct 23, 2026
0170b0d
fix(assets): use readable collision-safe asset names (29)
Oct 23, 2026
dcf3441
fix(codegen): honor confirmed responsive pairs and retain outputs on …
Oct 23, 2026
6fe52d2
fix(export): complete feedback 37 asset-only reply semantics
Oct 23, 2026
6ff0b10
fix(assets): bound default WebP sources and preserve flat alpha pixel…
Oct 23, 2026
c38bebc
fix(export): preserve multi-output split recovery for feedback 28
Oct 23, 2026
1bc41f2
fix(export): retain hidden asset exclusions and artifact warnings for…
Oct 23, 2026
45d48a6
fix(assets): require review of acquired but unwritten placeholders (31)
Oct 23, 2026
ca95754
fix(assets): reconcile source-fill results with requested job rows (39)
Oct 23, 2026
9949c28
test(codegen): align responsive refusal recovery expectations
Oct 23, 2026
2a2d862
fix(codegen): export component sets as one props-based component
Oct 23, 2026
4c83715
fix(skills): prioritize structured user decisions after confirmation
Oct 23, 2026
df29375
fix(runtime-audit): explain child-only interaction state changes
Oct 23, 2026
822bd58
fix(screen-plan): match ancestor plans to the requested app (34)
Oct 23, 2026
76d5e55
test(screen-plan): verify section device frames share responsive rout…
Oct 23, 2026
42a96d4
fix(project-plan): retain unanswered intake questions on draft (01)
Oct 23, 2026
b786f39
fix(runtime-audit): exclude clipped marquees and recognize descending…
Oct 23, 2026
467e664
fix(project-plan): keep status summaries compact (03)
Oct 23, 2026
c009128
fix(screen-plan): block completion until every URL is verified (33)
Oct 23, 2026
813bc11
fix(project-plan): apply new feature answers in one draft (09)
Oct 23, 2026
cfef5cc
fix(screen-plan): report frame owning pages in status and audit plans…
Oct 23, 2026
4e7bcf7
test(project-plan): reproduce Korean specification discovery (06)
Oct 23, 2026
e7634c7
fix(validation): reject unfinished data wiring and missing assets
Oct 23, 2026
8857d01
fix(screen-plan): prefer final pages and pin linked owning pages (43)
Oct 23, 2026
c811614
test(project-plan): retain decisions across answers-only redrafts (10)
Oct 23, 2026
fd073aa
test(project-plan): reject jsonb before transactional model writes (24)
Oct 23, 2026
d6fafac
fix(screen-plan): expose actionable page inventory truncation details…
Oct 23, 2026
266326d
fix(project-plan): report written models and unsplit state (21)
Oct 23, 2026
f0f569c
test(project-plan): expose Figma prerequisite before confirm (11)
Oct 23, 2026
dea9c60
test(project-plan): verify merged screen answer forwarding (17)
Oct 23, 2026
dd01d8f
test(project-plan): report exported entity prerequisites (25)
Oct 23, 2026
4d08a58
test(project-plan): distinguish static pages from form integrations (04)
Oct 23, 2026
7435855
fix(screen-plan): ask base page only for competing screens (43)
Oct 23, 2026
a0caba6
fix(screen-plan): queue missing Figma theme tokens before pages (38)
Oct 23, 2026
7753b90
test(project-plan): verify shared table ownership and static layer pr…
Oct 23, 2026
eb6afcc
fix(project-plan): satisfy compact status lint gate (03)
Oct 23, 2026
4312afc
fix(screen-plan): keep page inventory shallow at section screens (12)
Oct 23, 2026
3521c9e
fix(screen-plan): keep planning theme projection outside router (38)
Oct 23, 2026
4436de2
fix(screen-plan): retain router source limit after progress attachmen…
Oct 23, 2026
38703a8
fix(screen-plan): retain page inventory through structural groups (12)
Oct 23, 2026
a3494c8
fix(screen-plan): persist the selected base page at confirmation (43)
Oct 23, 2026
daf1c5b
fix(screen-plan): recognize owning page ids in draft scope (12)
Oct 23, 2026
d6aa5a6
fix(project-plan): guide API scopes without ERD entities (05)
Oct 23, 2026
8c472e7
fix(project-plan): report persisted decision count (20)
Oct 23, 2026
87ab0e1
test(validation): bind polymorphic input fixtures to a form name for …
Oct 23, 2026
5d751ed
fix(project-plan): provide executable confirm recovery (01)
Oct 23, 2026
630c392
fix(project-plan): merge shared feature question topics (04)
Oct 23, 2026
04e5823
fix(screen-plan): retain state variants in audits and export guidance…
Oct 23, 2026
ddd6f0a
fix(project-plan): bind ERD choices to actual entities (05)
Oct 23, 2026
095d7ba
fix(project-plan): summarize draft record changes (10)
Oct 23, 2026
40f3dcc
fix(project-plan): forward shared site facts as screen answers (17)
Oct 23, 2026
4934d9a
fix(assets): resolve omitted vector formats to SVG (30)
Oct 23, 2026
87baf6c
fix(assets): review code-only and unmapped asset URLs (31)
Oct 23, 2026
fbeca52
fix(project-plan): scope worker route and file contracts (22)
Oct 23, 2026
40e424a
fix(screen-plan): make draft route recommendations collision-free (18)
Oct 23, 2026
eb8e608
fix(project-plan): prioritize confirmed scope overrides (23)
Oct 23, 2026
c6050e4
fix(project-plan): verify runtime backend prerequisites (25)
Oct 23, 2026
1fdaf46
fix(codegen): stabilize diagnostic color precision
Oct 23, 2026
4d82111
fix(project-plan): add supervisor integration stage (40)
Oct 23, 2026
31a3fa7
fix(project-plan): flag unbound data operation contracts (41)
Oct 23, 2026
1fd5756
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-delivery' i…
Oct 23, 2026
fa29842
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-assets' int…
Oct 23, 2026
0636c12
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-codegen' in…
Oct 23, 2026
d1b14c1
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-screen' int…
Oct 23, 2026
5328559
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-plan' into …
Oct 23, 2026
77f7fe3
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-validate' i…
Oct 23, 2026
a07388a
test(assets): refresh wquw_151 frame snapshots for readable asset nam…
Oct 23, 2026
d9eda63
test(runtime-audit): write the real-render hero fixture at its readab…
Oct 23, 2026
a785103
fix(project-plan): resolve inherited serde prerequisites (25)
Oct 23, 2026
488bbbc
fix(skills): verify with lint fix before manual leftovers (45)
Oct 23, 2026
543f251
fix(project-plan): group domain workers and shared parts (40)
Oct 23, 2026
213d159
fix(validation): group prop sorting into one lint fix action (45)
Oct 23, 2026
4ee28fd
fix(skills): label lint fix formatting obligations explicitly (45)
Oct 23, 2026
5fe355e
fix(skills): warn that oxlint owns template formatting (45)
Oct 23, 2026
f24686f
fix(project-plan): derive endpoint contract drafts from screens (41)
Oct 23, 2026
de9c362
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-validate' i…
Oct 23, 2026
feeea4e
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-plan' into …
Oct 23, 2026
46a8566
fix(project-plan): infer and verify endpoint contracts (41)
Oct 23, 2026
68b6720
fix(project-plan): infer owned unit file contracts (22)
Oct 23, 2026
3833e1a
fix(project-plan): document inferred contract evidence (41)
Oct 23, 2026
880787e
fix(project-plan): require camelCase API contract names (41)
Oct 23, 2026
453a83b
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-plan' into …
Oct 23, 2026
5c4a3a4
fix(figma-search): report bad limit and match kind as invalid input
Oct 23, 2026
25de36b
fix(project-plan): return structured stale status recovery (02)
Oct 23, 2026
0b271c2
fix(figma-search): accept omitted type-list queries and classify bad …
Oct 23, 2026
7a37576
fix(project-plan): preserve empty-prefix header capacity (07)
Oct 23, 2026
a3c1adf
fix(codegen): resolve component export roots to their collected sets …
Oct 23, 2026
dec2947
fix(figma-search): load pages without switching and disclose candidat…
Oct 23, 2026
b2223c9
fix(project-plan): block destructive source model replacement (55)
Oct 23, 2026
baf6690
fix(figma-search): flag hidden ancestry and rank live designs first (08)
Oct 23, 2026
bad8224
fix(codegen): require explicit figmaNodeIds export opt-in (64)
Oct 23, 2026
db5825a
fix(project-plan): respect operation evidence and API model paths (54)
Oct 23, 2026
63d901d
fix(project-plan): report column type violations and disk drift (51)
Oct 23, 2026
daead12
fix(codegen): resolve instance references from component identity (65)
Oct 23, 2026
94d3b09
fix(project-plan): offer existing plan recovery and archived restart …
Oct 23, 2026
018a970
fix(codegen): put anchor-owned states in export wiring guidance (44)
Oct 23, 2026
de8e4d5
fix(codegen): verify component-set export contracts and bounded guida…
Oct 23, 2026
0d2c6ef
fix(bridge-export): accept single frame references and report unavail…
Oct 23, 2026
dfbad20
test(export): cover oversized selection preserving file writes (35)
Oct 23, 2026
44821c9
test(figma-search): cover read-only page loading (50)
Oct 23, 2026
f4c5369
fix(project-plan): persist exclusions and evidence-based recommendati…
Oct 23, 2026
cb760be
fix(skills): require structured user decisions across workflows (42)
Oct 23, 2026
880e58d
fix(screen-plan): guard explicit navigation target routes (14)
Oct 23, 2026
28b2645
fix(screen-plan): guard device frames across six sections (15)
Oct 23, 2026
19ad0f7
fix(screen-plan): condense recommended proposal decisions (53)
Oct 23, 2026
ac6715f
fix(runtime-audit): emit every imported runner module (61)
Oct 23, 2026
b48fbec
fix(screen-plan): check metadata in the planned app (56)
Oct 23, 2026
9247c68
fix(skills): make bridge setup agent-operable (48)
Oct 23, 2026
e11c43f
fix(runtime-audit): isolate measurement errors and guard body probes …
Oct 23, 2026
cc86a29
fix(screen-plan): find ancestor plans for app audit requests (58)
Oct 23, 2026
8732d61
fix(skills): stop requiring audit plugin in app build config (64)
Oct 23, 2026
be4ffbb
fix(screen-plan): refresh plans independently of runner collisions (59)
Oct 23, 2026
42b275a
fix(skills): extract components before assembling pages (65)
Oct 23, 2026
53df39d
fix(screen-plan): gate audit checks on source anchors and references …
Oct 23, 2026
e74d9cd
fix(skills): preserve screen-plan question guidance (42)
Oct 23, 2026
c92c6e9
fix(skills): recognize bridge setup as an own skill in install test (48)
Oct 23, 2026
9f926f9
fix(screen-plan): validate plan before writing runner files (59)
Oct 23, 2026
53440e9
fix(runtime-audit): update emitted module integration contract (61)
Oct 23, 2026
64597fe
fix(screen-plan): return exact app dev server start commands (60)
Oct 23, 2026
4bf784d
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-codegen' in…
Oct 23, 2026
4eb6709
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-delivery' i…
Oct 23, 2026
68151e8
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-plan' into …
Oct 23, 2026
4564dae
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-screen' int…
Oct 23, 2026
90407b5
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-assets' int…
Oct 23, 2026
ce4fc12
Merge branch 'devfive/3099154426bffbeef8b62999b80bb038/fb-validate' i…
Oct 23, 2026
691052a
chore: add changepack for feedback batch 2-66
Oct 23, 2026
3b3a794
fix(codegen): componentTsx for real component sets
Oct 24, 2026
cee9d4f
fix(codegen): trace unrepresented variant children, dedupe declared t…
Oct 24, 2026
ad02a4b
fix(codegen): emit type-correct componentTsx for responsive scalar pr…
Oct 24, 2026
62b0fdc
fix(codegen): keep single-source responsive src scalar and keep leaf …
Oct 24, 2026
90b8521
fix(codegen): resolve variant expressions by raw Figma property key a…
Oct 24, 2026
0dc21e6
fix(ui_validate): accept devup-ui props on primitives, resolve as/pro…
Oct 24, 2026
6562417
fix(navigation): a props={{ href }} binding marks a rendered-as-link …
Oct 24, 2026
be10470
fix(screen_plan): confirm/assume/defer/status find the plan the repos…
Oct 24, 2026
28ab567
fix(screen_plan): status maps a static route before a dynamic sibling
Oct 24, 2026
6fddf22
fix(screen_plan): the inventory note reports only omitted routes or u…
Oct 24, 2026
a6f8e6c
fix(skills): an app directory sees skills installed at its repository…
Oct 24, 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
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_eRTXLDvy0yMJ3YwboDGWu.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"Cargo.toml": "Patch"
},
"note": "componentTsx: fix button set panic, expose component text as unique props even when declared names normalize alike, drop set-canvas placement, emit undefined for absent variant values, preserve scalar typography and image sources across responsive variants including one-sided sources with explicit responsive visibility, prevent leaf variant image sources from leaking onto containers, declare referenced boolean props, emit valid interaction selectors, fix defect G by resolving variant values with original Figma property keys to preserve icon sources and state styles and keeping boolean-controlled hidden layers out of flattened assets, and account explicitly for unrepresented variant children without claiming emitted property provenance",
"date": "2026-10-24T00:21:08.585604600Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_jojyOPeLAlr8D8DSurB-c.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"Cargo.toml": "Patch"
},
"note": "screen plan follows the final design page: state-variant frames fold into their route, remap moves routes to a newer frame; runtime audit treats overflow:auto scrollers as reachable when headless Chromium hides scrollbars",
"date": "2026-10-22T08:15:22.677086Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_vh0-IGr9a1CbP3nVaXSf2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"Cargo.toml": "Patch"
},
"note": "feedback batch 2-66: componentTsx exports component sets/variants/instances, data-figma-node-id opt-in (figmaNodeIds), search lists by type across pages, bridge referencePng frameIds, project-plan never drops on-disk columns and recovers existing plans, screen-plan condensed proposals and monorepo auditPlan lookup, runtime-audit emits all runner modules and isolates probe errors, figma-bridge-setup skill and component-first workflow",
"date": "2026-10-23T17:54:27.176404300Z"
}
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ devup-mcp가 Figma에 붙는 경로는 **둘**이고, 대등하지 않습니다.

**1. Figma 데스크톱 앱에서 불러옵니다**

Figma의 플러그인 등록은 데스크톱 앱의 메뉴 동작이라 스크립트로 대신할 수 없습니다. 이 세 단계만 직접 하시면 됩니다.
에이전트는 `devup_skills install`로 배포되는 `figma-bridge-setup` 스킬을 읽고 computer use로 아래 메뉴를 직접 조작합니다. 먼저 status에서 host/server commit·buildId를 비교하고, `inView:false`이면 Figma 창을 앞으로 가져와 해당 파일 탭을 누릅니다. Figma가 없으면 question 도구로 데스크톱 설치와 metered direct OAuth 중에서 묻습니다. 오래된 host는 PID·경로·소유 클라이언트를 확인한 뒤 그 연결만 재시작하며, 다른 세션을 중단하기 전에는 승인을 받습니다. 마지막에 status와 작은 노드 읽기로 연결을 확인합니다.

1. **Figma 데스크톱 앱**을 엽니다 (브라우저판은 로컬 플러그인을 못 불러옵니다)
2. 메뉴에서 **Plugins → Development → Import plugin from manifest...**
Expand Down Expand Up @@ -1078,7 +1078,7 @@ snapshot에 없는 목적지(legacy 경로, 다중 루트 요청)는 조용히
규칙은 셋입니다: **모든 화면·섹션·에셋을 그려진 모든 폭에서 시각으로 검증한다**, **기다리느라 멈추지 않고 작업 순서를 바꾼다**, **완료 = 실패 0, 아니면 증거와 함께 unresolved로 남긴다**.

- `devup_screen_plan` `action: "auditPlan"`(확정 뒤)은 확정된 URL마다 runtime audit 계획을 `.devup/runtime-audit/<slug>/plan.json`에 쓰고 실행기를 `.devup/runtime-audit/runner/`에 한 번 씁니다. `artifacts`에 그 계획 프레임들의 `devup_figma_export` artifactId를 모두 넘깁니다. 폭은 그려진 폭(뷰포트 높이 = 프레임 높이) + 1440/1100/858/390입니다. 모든 폭에서 page health(페이지 오류·콘솔 오류·"This page couldn't load"/"Application error" 텍스트 없음), 가로 넘침, 잘린 텍스트, 내비 잘림을 보고, 그려진 폭에서는 페이지 전체를 referencePng와, 프레임의 최상위 자식(섹션)과 에셋을 각자의 native-node 렌더와 비교하고, 섹션·카드·에셋의 크기와 **위치**(`bounds` 검사; 카드 순서가 틀리면 실패)를 봅니다. 링크·버튼 전체의 hover/focus와 다음 URL로의 view-transition 검사도 들어가며, view-transition은 코드가 맨 뒤로 보내고 실행기가 끝나면 원래 URL로 돌아옵니다. 빠진 referencePng·렌더·프레임은 `missingReferences`에 만드는 export 호출과 함께 나옵니다 — 조용히 건너뛰지 않습니다.
- 섹션·카드·에셋은 `data-figma-node-id`로 찾습니다. `projectRoot`가 있는 export는 페이지 루트, 섹션, 그 자식, 에셋에 이 속성을 씁니다(projectRoot 없는 export는 플러그인과 같은 출력 그대로). 끄려면 설정 `figmaNodeIds: false`, 프로덕션에서 빼려면 Next의 `compiler.reactRemoveProperties: { properties: ["^data-figma-node-id$"] }`.
- Figma 노드 속성은 기본으로 끕니다. export 스키마가 지원할 때만 `figmaNodeIds: true`로 켜고, 속성이 없는 검사를 통과로 간주하지 않습니다.
- `status`에 `auditPlanPath`·`auditReportPath`를 주면 모든 실패를 분류합니다: `runtime-error`, `overflow`, `layout-position`, `layout-size`, `asset-mismatch`(멀티컬러 SVG에 mask, 잘못된 파일, crop/objectFit 순으로 원인 제시), `section-visual`, `page-visual`, `interaction`, `view-transition` — 각각 Figma 노드, 코드 파일, 구체적인 수정 힌트를 담습니다. 반환된 `visualComparisons` 호출(`threshold`·`diffPath` 포함)을 돌리고 결과를 `visualResults`로 넘깁니다. 안티앨리어싱은 실패가 아닙니다: 바뀐 픽셀 비율 허용치가 페이지·섹션 3%, 에셋 5%이고, 설정 `visualThresholds`(`page`, `section`, `asset`, 노드별 `nodes`)로 조정합니다.
- 같은 finding은 새 실행마다 한 번만 시도로 셉니다. `maxFixAttempts`(설정, 기본 3)번 고치고도 실패하면 스크린샷·diff 경로와 함께 `unresolved`로 기록하고 작업 큐에서 뺍니다. `workQueue`는 URL마다 build → audit → fix → compare 순이고 막힌 일은 뒤로 갑니다.
- 확정 뒤 생긴 결정은 `action: "defer"`(`decision: {id, about, question, options, recommended, destructive}`)로 남기고 계속 일합니다. 마지막에 `assume`(`about`: 결정 id)으로 추천안을 택하고, 파괴적인 결정은 만들지 않은 채 보고서에 남습니다. 실행기를 설치할 수 없으면 `toolingUnavailable`로 그 URL의 audit를 inconclusive로 두고 계속합니다.
Expand All @@ -1097,7 +1097,7 @@ Figma 프레임은 정적으로 변환되므로, 그려진 폭에서 크기·위

동적 라우트(`[slug]`, `[id]`, catch-all)는 `notAudited`로 넘기지 않고 실제 URL로 검사합니다: 라우트의 `generateStaticParams`(리터럴, 또는 읽는 로컬 데이터 파일) → 이미 검사한 페이지의 `a[href]`(실행기가 페이지마다 모으고 다음 `auditPlan`이 사용) → 매핑된 Figma 상세 프레임의 제목으로 만든 샘플(가정으로 기록) 순서이며, 10개 이하면 전부 검사합니다. 셋 다 없을 때만 이유와 함께 `notAudited`입니다.

`data-figma-node-id(s)`는 프로덕션에 남지 않습니다. vinext와 devup-ui Vite 플러그인에는 속성을 지우는 옵션이 없어서, `auditPlan`이 각 앱의 vite.config 옆에 작은 Vite 플러그인(`vite-plugin-strip-figma-node-ids.mjs`)을 둡니다. `enforce: 'pre'`, `apply`가 `vite build`일 때만이며 `DEVUP_AUDIT=1`이면 남깁니다. Rolldown은 transform 훅 뒤에 TSX/JSX를 변환하므로 이 단계에서는 속성이 그대로 JSX 텍스트입니다. `plugins: [stripFigmaNodeIds(), vinext()]`로 연결하고, audit는 dev 서버나 `DEVUP_AUDIT=1 bun run build`로 돌리며, 제출 체크리스트는 빌드 출력에서 `grep -rl data-figma-node-id dist`가 아무것도 찾지 않아야 통과합니다. `next` CLI에서는 `compiler.reactRemoveProperties`를 씁니다.
DevupUI가 audit 속성을 기본으로 제거하며 `DEVUP_AUDIT=1`이면 유지합니다. audit를 위해 앱의 빌드 설정을 바꾸거나 별도 strip 플러그인을 등록하지 않습니다. 일반 프로덕션 빌드 산출물에 `data-figma-node-id(s)`가 없는지 확인합니다. 이전 서버가 별도 플러그인 파일을 반환해도 앱 설정에 연결하지 않습니다.

최신 스택이 기본입니다: Next App Router를 vinext(Vite)로 실행하고, bun workspaces(`apps/*`), devup-ui, oxlint(eslint-plugin-devup), 최신 버전. `vinext` 의존성·`vinext` 스크립트·vinext를 import하는 `vite.config`가 있으면 `next` 의존성이 없어도 App Router 앱으로 보고, `devup_project_context`의 ui/routes가 앱별 `frameworks`를 알려 줍니다. 구현 전에는 묻지 않고 저장소 루트에서 `bunx @dependency-check-updates/cli -d -u --rm`(-d: 루트 아래 모든 manifest, -u: 복구 가능한 일괄 적용, --rm: 각 manifest 옆의 lockfile·설치 디렉터리 삭제 — Cargo.lock·target/ 포함) → `bun install` → build·lint·typecheck를 하고, 라이브러리 API는 기억이 아니라 현재 문서를 봅니다. 결과는 `assume`의 `dependencyUpdate`로 남기고 `status`에 dependencies 줄로 나옵니다. 앱이 없으면 `dev-five-git/service-template`에서 시작하고(이름·구성만 질문), `next` CLI 프로젝트에는 "Migrate to vinext (Recommended) / Keep next"를 한 번 묻습니다. 설정 파일의 `framework`·`updateDependencies: false`로 미리 답할 수 있습니다.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ pub fn generate_component(
root_id: &str,
options: &CodegenOptions,
) -> Result<CodegenOutput, DevupError> {
if let Some(root) = snapshot.nodes.get(root_id)
&& root.node_type == "COMPONENT_SET"
{
return match variant::generate_variant_component_set(snapshot, root_id, options, true)? {
Some(mut output) => {
output.tsx.insert_str(0, &format!("import {{ {} }} from \"@devup-ui/react\";\n\n", output.imports.join(", ")));
finalize_codegen_output(output, snapshot, options, root_id)
},
None => generate_component_set_target(snapshot, root_id, root.typed_view().name().unwrap_or("Component"), options),
};
}
let generated = generate_node_marked(snapshot, root_id, options)?;
let root = snapshot.nodes.get(root_id).ok_or_else(|| {
DevupError::new(
Expand Down Expand Up @@ -324,7 +335,7 @@ pub fn generate_component_set_target(
let root_view = component_set.unwrap_or(root).typed_view();
let main = component_set.is_some();
if let Some(set) = component_set
&& let Some(output) = variant::generate_variant_component_set(snapshot, &set.id, options)?
&& let Some(output) = variant::generate_variant_component_set(snapshot, &set.id, options, false)?
{
return finalize_codegen_output(output, snapshot, options, &set.id);
}
Expand Down Expand Up @@ -919,4 +930,4 @@ fn js_value(value: &serde_json::Value, depth: usize) -> String {

fn json_string(value: &str) -> String {
serde_json::to_string(value).expect("string JSON serialization")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,19 @@ fn finalize_codegen_output(
.extend(unresolved_token_bindings(snapshot, root_id, options));
output.projection_trace =
build_projection_trace(snapshot, root_id, &output.tsx, &output.source_map);
for diagnostic in &output.diagnostics {
if diagnostic.code == "DEVUP_CODEGEN_VARIANT_CHILD_FALLBACK"
&& let Some(node_id) = &diagnostic.node_id
&& !output.projection_trace.entries.iter().any(|entry| &entry.node_id == node_id)
{
output.projection_trace.entries.push(crate::provenance::ProjectionTraceEntry {
node_id: node_id.clone(),
disposition: crate::provenance::ProjectionDisposition::Ignored,
reason: "unrepresented-variant-child".into(),
generated_range: None,
});
}
}
output.fidelity_report = validate_fidelity(snapshot, root_id, &output)?;
output
.diagnostics
Expand Down Expand Up @@ -555,4 +568,4 @@ pub(super) enum PropValue {
String(String),
}

pub(super) type Prop = (String, PropValue);
pub(super) type Prop = (String, PropValue);
28 changes: 25 additions & 3 deletions crates/devup-mcp-devup-ui/src/codegen/component/render_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,18 @@ fn render_node(
.and_then(|references| references.get("visible"))
.and_then(serde_json::Value::as_str)
.map(component_property_name);
let component = snapshot
let master = view.string("mainComponentId")
.or_else(|| view.string("componentId"))
.and_then(|id| snapshot.nodes.get(id));
let set = view.string("mainComponentSetId")
.or_else(|| master.and_then(|node| node.typed_view().string("parentId")))
.and_then(|id| snapshot.nodes.get(id))
.filter(|node| node.node_type == "COMPONENT_SET");
let component = set
.and_then(|node| node.typed_view().name())
.map(normalize_component_name)
.or_else(|| master.and_then(|node| node.typed_view().name()).map(normalize_component_name))
.unwrap_or_else(|| snapshot
.nodes
.values()
.filter(|candidate| candidate.typed_view().node_type() == "COMPONENT")
Expand All @@ -80,8 +91,19 @@ fn render_node(
})
.max_by_key(|name| name.len())
.map(legacy_component_name)
.unwrap_or_else(|| legacy_component_name(view.name().unwrap_or("Component")));
let content = main_property.unwrap_or_else(|| format!("<{component} />"));
.unwrap_or_else(|| legacy_component_name(view.name().unwrap_or("Component"))));
let mut properties = super::variant::instance_variant_props(&view);
for (name, value) in view.value("variantProperties").and_then(serde_json::Value::as_object).into_iter().flatten() {
if !["effect", "viewport"].iter().any(|reserved| name.eq_ignore_ascii_case(reserved))
&& let Some(value) = value.as_str()
{
properties.entry(super::variant::instance_property_name(name)).or_insert_with(|| value.to_owned());
}
}
let attributes = properties.iter().map(|(name, value)| {
format!(" {name}={{{}}}", serde_json::to_string(value).unwrap())
}).collect::<String>();
let content = main_property.unwrap_or_else(|| format!("<{component}{attributes} />"));
let expression = if let Some(visible) = visible_property {
format!("{{{visible} && {content}}}")
} else if references.is_some_and(|references| references.contains_key("mainComponent")) {
Expand Down
50 changes: 50 additions & 0 deletions crates/devup-mcp-devup-ui/src/codegen/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,32 @@ pub(super) fn placement_evidence(snapshot: &Snapshot, node_id: &str) -> Value {
original
}

fn stable_diagnostic_colors(value: &mut Value) {
match value {
Value::Object(object) => {
if ["r", "g", "b"]
.iter()
.all(|key| object.get(*key).is_some_and(Value::is_number))
{
for key in ["r", "g", "b", "a"] {
if let Some(channel) = object.get_mut(key)
&& channel.as_number().is_some_and(serde_json::Number::is_f64)
&& let Some(number) = channel.as_f64()
{
*channel = json!((number * 1e12).round() / 1e12);
}
}
} else {
for child in object.values_mut() {
stable_diagnostic_colors(child);
}
}
}
Value::Array(array) => array.iter_mut().for_each(stable_diagnostic_colors),
_ => {}
}
}

fn geometry(node: &devup_mcp_figma::RawNode) -> Value {
let view = node.typed_view();
let mut value = json!({"nodeId":node.id,"nodeType":node.node_type});
Expand Down Expand Up @@ -567,6 +593,9 @@ fn geometry(node: &devup_mcp_figma::RawNode) -> Value {
"fills",
] {
value[field] = view.value(field).cloned().unwrap_or(Value::Null);
if matches!(field, "fills" | "effects") {
stable_diagnostic_colors(&mut value[field]);
}
}
let missing: Vec<_> = [
"x",
Expand Down Expand Up @@ -597,3 +626,24 @@ pub(super) fn fallback_original(snapshot: &Snapshot, node_id: &str, property: &s
.unwrap_or(Value::Null),
}
}

#[cfg(test)]
mod diagnostic_color_tests {
use super::*;

#[test]
fn geometry_color_diagnostics_ignore_last_bit_parse_differences() {
let diagnostic = |blue| {
let node = serde_json::from_value(json!({"id":"icon","type":"VECTOR","fields":{
"width":24.000000000001,"fills":[{"type":"SOLID","color":{"r":1,"g":0,"b":blue}}]
}}))
.unwrap();
geometry(&node)
};
let first = diagnostic(0.9244915843009948);
let second = diagnostic(0.9244915843009949);
assert_eq!(first, second);
assert_eq!(first["fills"][0]["color"]["b"], json!(0.924491584301));
assert_eq!(first["width"], json!(24.000000000001));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(super) fn project_tree(
drawn_when: Vec::new(),
leading_comment: None,
slots: Vec::new(),
scalar_sources: Vec::new(),
};
let visible = node.typed_view().value("fills").and_then(Value::as_array);
if tree.component == "Image" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub(super) fn push_layout_props(
let is_page_root = parent
.map(|parent| parent.typed_view().node_type())
.or_else(|| view.string("parentType"))
.is_some_and(|kind| matches!(kind, "SECTION" | "PAGE" | "COMPONENT_SET"));
.is_some_and(|kind| matches!(kind, "SECTION" | "PAGE"));
let fixed_w = view.string("layoutSizingHorizontal") == Some("FIXED");
let fixed_h = view.string("layoutSizingVertical") == Some("FIXED");
let fill_w = view.string("layoutSizingHorizontal") == Some("FILL");
Expand Down Expand Up @@ -227,8 +227,7 @@ pub(super) fn push_layout_props(
(own_height, parent_height),
(Some(height), Some(parent_height)) if parent_height > height
);
let in_a_group =
parent.is_some_and(|parent| parent.typed_view().node_type() == "GROUP");
let in_a_group = parent.is_some_and(|parent| parent.node_type == "GROUP");
let leaf_shape = !has_children && !holds_children && !is_asset && in_a_group;
if leaf_shape && parent_wider {
width = own_width.map(px);
Expand Down Expand Up @@ -965,4 +964,4 @@ fn child_shrinker(parent: &RawNode, dimension: &str) -> bool {
pub(super) struct GridTemplate {
pub css: Option<String>,
pub unverified: Vec<&'static str>,
}
}
1 change: 1 addition & 0 deletions crates/devup-mcp-devup-ui/src/codegen/responsive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@

// Source sections share this module to preserve privacy and test names.
include!("responsive/breakpoint_merge.rs");
include!("responsive/width_label.rs");
include!("responsive/merge_content.rs");
include!("responsive/impl.rs");
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ pub fn is_breakpoint_name(name: &str) -> bool {

/// Explicit width suffixes can describe one named screen. No fuzzy matching.
pub fn family_name(name: &str) -> String {
let name = name.trim().to_ascii_lowercase();
let name = strip_width_label(name.trim().to_ascii_lowercase());
if is_breakpoint_name(&name) {
return "@breakpoint-labels".into();
}
Expand Down
Loading
Loading