From 954829f5f8eb940c5087e9bd8058654229458b52 Mon Sep 17 00:00:00 2001 From: Dowon Park Date: Tue, 14 Jul 2026 00:24:02 +0900 Subject: [PATCH 1/4] =?UTF-8?q?test(#221):=20PR2-T1/T2=20=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=ED=99=95=EC=9E=A5(RED=20by=20design)=20?= =?UTF-8?q?=E2=80=94=20fail=20=ED=95=B4=EC=A0=9C=C2=B7G10/G10b/G11/G12?= =?UTF-8?q?=C2=B7R1=20=ED=8C=A8=EB=84=90=20=ED=99=95=EC=9E=A5=C2=B7?= =?UTF-8?q?=ED=95=80=204=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RED 4건(G1/G9 채팅·프로젝트, G10 채팅 스트립, G10b complete 러너 프로젝트 스트립) 실발화 확인. R5 배너 e2e 는 계획 강등 경로 채택(핀+라이브 — 단절 하니스 재사용 과중). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CkNHx6UJ5U24PUbpnQycFp --- e2e/mobile-responsive.web.e2e.ts | 107 ++++++++++++++++++++++++- src/renderer/styles.responsive.test.ts | 18 +++++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/e2e/mobile-responsive.web.e2e.ts b/e2e/mobile-responsive.web.e2e.ts index ae796eb..1b273cf 100644 --- a/e2e/mobile-responsive.web.e2e.ts +++ b/e2e/mobile-responsive.web.e2e.ts @@ -93,8 +93,8 @@ test.describe('#221 모바일 반응형(PR1 셸) — 390×844', () => { ['채팅', '.chat', '.chat-main'], ['프로젝트', '.project-layout', '.project-main'], ] as const) { - test(`G1/G9 ${tab} 탭 폰 1열 스택 + .main 가로 오버플로 0 — PR2 활성화`, async ({ page }) => { - test.fail(true, `PR2(#221): ${layoutSel} 232px 그리드 미수정 — 1열 전환 후 활성화`) + test(`G1/G9 ${tab} 탭 폰 1열 스택 + .main 가로 오버플로 0`, async ({ page }) => { + // PR2(#221): test.fail 해제 — 1열 전환 후 강제 활성화(계획 §3 T1). await openApp(page, server.url, 390, 844) await switchTab(page, tab) const o = await overflowOf(page) @@ -136,6 +136,45 @@ test.describe('#221 모바일 반응형(PR1 셸) — 390×844', () => { await expect(page.locator('.project-main')).toHaveCount(1) }) + // G10(Codex 스펙 1R 하드 계약): 시드 방 1개는 스트립이 오버플로하지 않아 vacuous — UI 경로로 + // 방을 늘려 오버플로 실존을 선단언한 뒤, 실 스크롤로 마지막 칩의 도달·클릭(조작성)을 증명한다. + test('G10 채팅 .rooms 칩 스트립 — 방 3개+ 생성→오버플로 실존→마지막 칩 도달·클릭·활성', async ({ + page, + }) => { + await openApp(page, server.url, 390, 844) + await switchTab(page, '채팅') + for (let i = 1; i <= 3; i++) { + await page.getByPlaceholder('새 방 이름').fill(`모바일-긴-작업방-이름-${i}`) + await page.getByRole('button', { name: '+', exact: true }).click() + await expect(page.locator('.room-btn')).toHaveCount(1 + i) // 시드 1 + 생성분 + } + const rooms = page.locator('.chat .rooms') + const overflow = await rooms.evaluate((el) => el.scrollWidth - el.clientWidth) + expect(overflow, '.rooms 가로 오버플로 실존(스트립 스크롤 표면)').toBeGreaterThan(0) + // 실 스크롤 → 마지막 칩이 뷰포트/스트립 안에 들어와 클릭 가능해진다. + await rooms.evaluate((el) => { + el.scrollLeft = el.scrollWidth + }) + const last = page.locator('.room-btn').last() + const box = await last.boundingBox() + expectInViewport(box, 390, 844, '마지막 룸 칩') + await last.click() + await expect(last).toHaveAttribute('data-active', 'true') + // G12: 방 선택 상태에서 composer 입력창이 뷰포트 안(엄지 도달). + const composer = page.getByPlaceholder('메시지 입력 (개입/지시)') + expectInViewport(await composer.boundingBox(), 390, 844, 'composer 입력창') + }) + + // G11: 프로젝트 폼 조작 가능(.grid-2 1열 전환 후 goal 입력이 실제로 동작). + test('G11 프로젝트 goal 입력 가능(폰)', async ({ page }) => { + await openApp(page, server.url, 390, 844) + await switchTab(page, '프로젝트') + const goal = page.getByPlaceholder(/사용자 인증/) + await goal.fill('모바일 폼 입력 계약') + await expect(goal).toHaveValue('모바일 폼 입력 계약') + expectInViewport(await goal.boundingBox(), 390, 844, 'goal 입력창') + }) + test('G3 내비 3탭 뷰포트내 앵커 + G4 터치 타깃 ≥44px(내비·위저드 bare 버튼·.btn 표본)', async ({ page, }) => { @@ -255,6 +294,70 @@ test.describe('#221 데스크톱 무회귀 가드 — 641×900(경계 최소 초 expect(computed.fieldFont, '.field(#mcp-servers) font-size').toBe('13px') expect(computed.bodyFont, 'body font-size').toBe('13px') expect(computed.tagVisible, '.brand .tag 가시(데스크톱)').toBe(true) + // R1 확장(PR2): 패널 그리드의 232px 사이드바 칼럼이 >640px 에서 유지 — 1열 규칙 유출 즉시 적발. + await switchTab(page, '채팅') + const chatCols = await page + .locator('.chat') + .evaluate((el) => getComputedStyle(el).gridTemplateColumns) + expect(chatCols, '.chat 데스크톱 232px 칼럼').toContain('232px') + await switchTab(page, '프로젝트') + const projCols = await page + .locator('.project-layout') + .evaluate((el) => getComputedStyle(el).gridTemplateColumns) + expect(projCols, '.project-layout 데스크톱 232px 칼럼').toContain('232px') }) } }) + +// G10b(Codex 계획 1R — 생성 메커니즘 확정분): 프로젝트 목록 항목은 런 시작으로만 생긴다(채팅의 +// 무비용 방 생성 경로 부재) — `complete` 러너 서버(별도 수명주기·Codex 계획 2R 노트)에서 UI 경로로 +// 런을 순차 완주시켜 3건+ 를 만들고, 프로젝트 탭 스트립에 채팅과 동급 하드 계약을 건다. +// src/main/e2e.ts 시드·엔진 API 무변경(isE2EActive 가드 불변). +test.describe('#221 PR2 — complete 러너(프로젝트 스트립·요약 오버플로)', () => { + let csrv: RunningWebServer + + test.beforeAll(async () => { + csrv = await startFleetWebServer({ FLEET_E2E_RUNNER: 'complete' }) + }) + test.afterAll(async () => { + await csrv?.stop() + }) + + test('G10b 프로젝트 .rooms 칩 스트립 — 런 3회 완주→오버플로 실존→마지막 칩 클릭 + G8 요약 긴 토큰', async ({ + page, + }) => { + await openApp(page, csrv.url, 390, 844) + await switchTab(page, '프로젝트') + // 런 3회 순차 완주 — web-orchestration 패턴(각 완주·버튼 재활성 대기 후 다음 런: Codex 계획 2R). + for (let i = 1; i <= 3; i++) { + await page.getByPlaceholder(/사용자 인증/).fill(`모바일-스트립-프로젝트-${i} 목표`) + await page.getByRole('button', { name: '오케스트레이션 실행' }).click() + await expect(page.getByText('최종 요약 / 누락 점검')).toBeVisible({ timeout: 45_000 }) + await expect(page.getByRole('button', { name: '오케스트레이션 실행' })).toBeEnabled() + } + // '새 프로젝트' 선택 버튼도 .room-btn — 항목 3 + 선택 버튼 1 = 4(스트립 오버플로에도 기여). + await expect(page.locator('.project-layout .room-btn')).toHaveCount(4) + const rooms = page.locator('.project-layout .rooms') + const overflow = await rooms.evaluate((el) => el.scrollWidth - el.clientWidth) + expect(overflow, '프로젝트 .rooms 가로 오버플로 실존').toBeGreaterThan(0) + await rooms.evaluate((el) => { + el.scrollLeft = el.scrollWidth + }) + const last = page.locator('.project-layout .room-btn').last() + expectInViewport(await last.boundingBox(), 390, 844, '마지막 프로젝트 칩') + await last.click() + await expect(last).toHaveAttribute('data-active', 'true') + // G8(e2e 층): 요약 표시 상태에서 긴 무공백 토큰을 주입해도 .main 가로 스크롤 0 + // (완주 러너 요약이 짧아 주입으로 대체 — 계획 §1 G8 주석 의무. .summary overflow-wrap 이 방어). + await page.evaluate(() => { + const s = document.querySelector('.summary') + if (s) s.textContent += ' /very/long/unbroken/path/' + 'x'.repeat(600) + }) + const o = await page.evaluate(() => { + const main = document.querySelector('.main') + if (!main) throw new Error('.main 부재') + return main.scrollWidth - main.clientWidth + }) + expect(o, '긴 토큰 주입 후 .main 가로 오버플로').toBeLessThanOrEqual(0) + }) +}) diff --git a/src/renderer/styles.responsive.test.ts b/src/renderer/styles.responsive.test.ts index 92d4941..b0eafdc 100644 --- a/src/renderer/styles.responsive.test.ts +++ b/src/renderer/styles.responsive.test.ts @@ -150,6 +150,24 @@ describe('#221 ≤640px 셸 블록 — 폰 분기 규칙 존재 핀(미감 수 }) }) +describe('#221 PR2 — 패널 1열 스택·칩 스트립·폼 핀(셀렉터 결합)', () => { + it('G9 .chat/.project-layout 폰 1열(232px 사이드바 해제)', () => { + expect(shell).toMatch(/\.chat,\s*\.project-layout\s*\{[^}]*grid-template-columns: *1fr/) + }) + + it('.rooms 가로 칩 스트립(overflow-x auto — C2 .modal-chips 선례)', () => { + expect(shell).toMatch(/\.rooms\s*\{[^}]*overflow-x: *auto/) + }) + + it('G11 .grid-2 폰 1열(elicitation·manual 역할 배정)', () => { + expect(shell).toMatch(/\.grid-2\s*\{[^}]*grid-template-columns: *1fr/) + }) + + it('.line-item 랩(칩·배지·삭제 버튼 겹침 방지)', () => { + expect(shell).toMatch(/\.line-item\s*\{[^}]*flex-wrap: *wrap/) + }) +}) + describe('#221 R10 — reduced-motion 규율(§6-3: 640px 블록 밖 신규 추가 금지)', () => { it('640px 블록 밖 prefers-reduced-motion 출현 = 기존 2곳 고정(블록 안 중첩은 자유)', () => { // 640px 블록 전부를 제거한 나머지에서 카운트 — 전역 블록(:991)·modal-card 블록(:1149) From 8070e2dbd61c3c6c1e8999f9c462aafe5fb3e38e Mon Sep 17 00:00:00 2001 From: Dowon Park Date: Tue, 14 Jul 2026 00:27:44 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(#221):=20PR2-T3/T4=20=E2=80=94=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90=201=EC=97=B4=20=EC=8A=A4=ED=83=9D=C2=B7rooms?= =?UTF-8?q?=20=EC=B9=A9=20=EC=8A=A4=ED=8A=B8=EB=A6=BD=C2=B7grid-2=201?= =?UTF-8?q?=EC=97=B4=C2=B7line-item=20=EB=9E=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .chat/.project-layout → 1fr + rows auto 1fr(암묵 auto 행의 본문 높이 붕괴 방지). .rooms 가로 스트립(비버튼 자식 flex:0 0 auto — 방 생성 입력 min-width·room-btn width 해제+max-width 캡). e2e 단언 2건은 스크롤 도달 계약으로 정정(scrollIntoViewIfNeeded — 폴드 아래/요약 스크롤 상태는 결함 아님). 모바일 e2e 12/12. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CkNHx6UJ5U24PUbpnQycFp --- e2e/mobile-responsive.web.e2e.ts | 4 ++++ src/renderer/styles.css | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/e2e/mobile-responsive.web.e2e.ts b/e2e/mobile-responsive.web.e2e.ts index 1b273cf..0a9f7ab 100644 --- a/e2e/mobile-responsive.web.e2e.ts +++ b/e2e/mobile-responsive.web.e2e.ts @@ -172,6 +172,8 @@ test.describe('#221 모바일 반응형(PR1 셸) — 390×844', () => { const goal = page.getByPlaceholder(/사용자 인증/) await goal.fill('모바일 폼 입력 계약') await expect(goal).toHaveValue('모바일 폼 입력 계약') + // 폴드 아래는 세로 스크롤로 도달하는 게 정상 계약 — 스크롤 후 뷰포트내(폭 이탈·오프스크린만 적발). + await goal.scrollIntoViewIfNeeded() expectInViewport(await goal.boundingBox(), 390, 844, 'goal 입력창') }) @@ -344,6 +346,8 @@ test.describe('#221 PR2 — complete 러너(프로젝트 스트립·요약 오 el.scrollLeft = el.scrollWidth }) const last = page.locator('.project-layout .room-btn').last() + // 런 완주 후 .main 이 요약까지 세로 스크롤된 상태 — 스트립까지 스크롤 복귀가 정상 도달 경로. + await last.scrollIntoViewIfNeeded() expectInViewport(await last.boundingBox(), 390, 844, '마지막 프로젝트 칩') await last.click() await expect(last).toHaveAttribute('data-active', 'true') diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 17a2307..3de5047 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -1306,4 +1306,38 @@ code.id { (Codex PR 1R P2 — .summary 와 동종 결함의 위저드 국소판). */ overflow-wrap: anywhere; } + + /* ── PR2: 패널 1열 스택 + 룸/프로젝트 칩 스트립 ───────────────────────── + 232px 사이드바 그리드는 390px 에서 본문을 짜부라뜨린다(오버플로 없는 조작 불능 — RED 정직성 + 실측). 사이드바를 위 행의 가로 칩 스트립으로 전환(C2 .modal-chips 선례 — iOS 모멘텀 기본). */ + .chat, + .project-layout { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; /* 스트립(auto) + 본문(잔여) — 암묵 auto 행은 본문 높이를 붕괴시킨다 */ + } + .rooms { + display: flex; + align-items: center; + gap: 8px; + overflow-x: auto; + } + .rooms > * { + flex: 0 0 auto; /* 비버튼 자식(eyebrow·방 생성 .row)도 스트립 안 공존(R4 — 계약 우선 초안 적발) */ + } + .rooms .field { + min-width: 140px; /* 방 생성 입력이 스트립 안에서 입력 가능한 최소 폭 */ + width: auto; + } + .room-btn { + width: auto; /* 기본 width:100%(세로 목록) 해제 — 칩 폭은 내용 기반 */ + max-width: 220px; /* 긴 제목은 기존 ellipsis 로 절단 */ + } + + /* 프로젝트 폼·세션 패널·로그 정돈 */ + .grid-2 { + grid-template-columns: 1fr; /* elicitation·manual 역할 배정 2열 → 1열 */ + } + .line-item { + flex-wrap: wrap; /* 칩·배지·삭제 버튼 겹침 방지(인라인 style 승격 — JSX 무변) */ + } } From ae4fcd77aae820e73d7c8f5379567639fa8e0997 Mon Sep 17 00:00:00 2001 From: Dowon Park Date: Tue, 14 Jul 2026 00:42:03 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(#221):=20PR2=20=EC=9E=90=EC=B2=B4=20?= =?UTF-8?q?=EC=A0=81=EB=8C=80=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?=E2=80=94=20G8=20vacuous=20=ED=95=B4=EC=86=8C(P1)=C2=B7chat=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=20safe-area=20=EC=A0=95=EC=82=B0(P2)=C2=B7?= =?UTF-8?q?=ED=95=80/=EB=8B=A8=EC=96=B8=20=EC=9D=B4=EC=A4=91=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 게이트 렌즈 P1: G8 주입이 칩 클릭 후 실행돼 .summary unmount 로 결정론적 무음 skip → 주입·측정을 클릭 이전으로 이동 + 부재 throw. P2: grid-template-rows auto 1fr 무핀 갭 → G9 핀 결합. 캐스케이드 렌즈 P2: .chat 160px 상수가 폰 safe-area additive 와 미정산(실기기 composer 폴드 아래·env=0 e2e blind) → max(0px, env-기본값) 차감 calc(env=0 이면 현행 동일). P3: .rooms .field width 140px 고정·overflowX computed 이중화·G10b 도달성 권위 주석·완주 대기 명시 timeout· line-item 인라인 drift 주석. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CkNHx6UJ5U24PUbpnQycFp --- e2e/mobile-responsive.web.e2e.ts | 39 +++++++++++++++++--------- src/renderer/styles.css | 19 +++++++++++-- src/renderer/styles.responsive.test.ts | 4 ++- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/e2e/mobile-responsive.web.e2e.ts b/e2e/mobile-responsive.web.e2e.ts index 0a9f7ab..bfd02df 100644 --- a/e2e/mobile-responsive.web.e2e.ts +++ b/e2e/mobile-responsive.web.e2e.ts @@ -151,6 +151,10 @@ test.describe('#221 모바일 반응형(PR1 셸) — 390×844', () => { const rooms = page.locator('.chat .rooms') const overflow = await rooms.evaluate((el) => el.scrollWidth - el.clientWidth) expect(overflow, '.rooms 가로 오버플로 실존(스트립 스크롤 표면)').toBeGreaterThan(0) + // 프로그램적 scrollLeft 는 overflow:hidden 에서도 동작 — 사용자 스크롤 가능성은 computed 로 + // 이중화(자체 적대 리뷰 P3-2: auto→hidden 회귀는 행동 단언만으론 GREEN). + const overflowX = await rooms.evaluate((el) => getComputedStyle(el).overflowX) + expect(overflowX, '.rooms overflow-x(사용자 스크롤 가능성)').toBe('auto') // 실 스크롤 → 마지막 칩이 뷰포트/스트립 안에 들어와 클릭 가능해진다. await rooms.evaluate((el) => { el.scrollLeft = el.scrollWidth @@ -335,8 +339,27 @@ test.describe('#221 PR2 — complete 러너(프로젝트 스트립·요약 오 await page.getByPlaceholder(/사용자 인증/).fill(`모바일-스트립-프로젝트-${i} 목표`) await page.getByRole('button', { name: '오케스트레이션 실행' }).click() await expect(page.getByText('최종 요약 / 누락 점검')).toBeVisible({ timeout: 45_000 }) - await expect(page.getByRole('button', { name: '오케스트레이션 실행' })).toBeEnabled() + // stale 요약 조기 매치여도 여기가 완주까지 재시도(런 중 라벨='실행 중…' — 자체 리뷰 P3-3): + // 대기 예산을 명시해 기본 10s 로 전가되지 않게 한다. + await expect(page.getByRole('button', { name: '오케스트레이션 실행' })).toBeEnabled({ + timeout: 45_000, + }) } + // G8(e2e 층): 요약 표시 상태에서 긴 무공백 토큰을 주입해도 .main 가로 스크롤 0 + // (완주 러너 요약이 짧아 주입으로 대체 — 계획 §1 G8 주석 의무. .summary overflow-wrap 이 방어). + // **칩 클릭보다 먼저** — 다른 칩 선택은 setSummary('') 로 .summary 를 unmount 시켜 주입이 + // 무음 skip 되는 결정론적 vacuous 였다(자체 적대 리뷰 P1-1). 부재는 throw = 게이트 파손 신호. + await page.evaluate(() => { + const s = document.querySelector('.summary') + if (!s) throw new Error('.summary 부재 — G8 주입 게이트 파손(요약 미표시 상태)') + s.textContent += ' /very/long/unbroken/path/' + 'x'.repeat(600) + }) + const o = await page.evaluate(() => { + const main = document.querySelector('.main') + if (!main) throw new Error('.main 부재') + return main.scrollWidth - main.clientWidth + }) + expect(o, '긴 토큰 주입 후 .main 가로 오버플로').toBeLessThanOrEqual(0) // '새 프로젝트' 선택 버튼도 .room-btn — 항목 3 + 선택 버튼 1 = 4(스트립 오버플로에도 기여). await expect(page.locator('.project-layout .room-btn')).toHaveCount(4) const rooms = page.locator('.project-layout .rooms') @@ -347,21 +370,11 @@ test.describe('#221 PR2 — complete 러너(프로젝트 스트립·요약 오 }) const last = page.locator('.project-layout .room-btn').last() // 런 완주 후 .main 이 요약까지 세로 스크롤된 상태 — 스트립까지 스크롤 복귀가 정상 도달 경로. + // scrollIntoViewIfNeeded 는 양축 스크롤이라 **가로 조작성 증명 권위는 G10(채팅)** — 여기는 + // 도달성(overflow 실존 + 클릭 가능)만 계약(자체 적대 리뷰 P3-1). await last.scrollIntoViewIfNeeded() expectInViewport(await last.boundingBox(), 390, 844, '마지막 프로젝트 칩') await last.click() await expect(last).toHaveAttribute('data-active', 'true') - // G8(e2e 층): 요약 표시 상태에서 긴 무공백 토큰을 주입해도 .main 가로 스크롤 0 - // (완주 러너 요약이 짧아 주입으로 대체 — 계획 §1 G8 주석 의무. .summary overflow-wrap 이 방어). - await page.evaluate(() => { - const s = document.querySelector('.summary') - if (s) s.textContent += ' /very/long/unbroken/path/' + 'x'.repeat(600) - }) - const o = await page.evaluate(() => { - const main = document.querySelector('.main') - if (!main) throw new Error('.main 부재') - return main.scrollWidth - main.clientWidth - }) - expect(o, '긴 토큰 주입 후 .main 가로 오버플로').toBeLessThanOrEqual(0) }) }) diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 3de5047..3f62e29 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -1315,6 +1315,15 @@ code.id { grid-template-columns: 1fr; grid-template-rows: auto 1fr; /* 스트립(auto) + 본문(잔여) — 암묵 auto 행은 본문 높이를 붕괴시킨다 */ } + .chat { + /* base 상수 160px 은 데스크톱 크롬 기준 — 폰 safe-area additive(topbar top·footer bottom)가 + 붙으면 크롬이 커져 composer 가 폴드 아래로 밀린다(자체 적대 리뷰 P2-1 — Playwright 는 env()=0 + 이라 못 잡는 실기기 한정 표면). additive 로 늘어난 만큼만 정확히 차감: env=0 이면 현행과 동일. */ + height: calc( + 100dvh - 160px - max(0px, env(safe-area-inset-top) - 10px) - + max(0px, env(safe-area-inset-bottom) - 7px) + ); + } .rooms { display: flex; align-items: center; @@ -1325,8 +1334,9 @@ code.id { flex: 0 0 auto; /* 비버튼 자식(eyebrow·방 생성 .row)도 스트립 안 공존(R4 — 계약 우선 초안 적발) */ } .rooms .field { - min-width: 140px; /* 방 생성 입력이 스트립 안에서 입력 가능한 최소 폭 */ - width: auto; + /* width:auto 는 input 고유폭(size 기본·16px 폰트서 ~190px)이라 스트립을 과점유 — 명시 고정 + (자체 적대 리뷰 P3-2). */ + width: 140px; } .room-btn { width: auto; /* 기본 width:100%(세로 목록) 해제 — 칩 폭은 내용 기반 */ @@ -1338,6 +1348,9 @@ code.id { grid-template-columns: 1fr; /* elicitation·manual 역할 배정 2열 → 1열 */ } .line-item { - flex-wrap: wrap; /* 칩·배지·삭제 버튼 겹침 방지(인라인 style 승격 — JSX 무변) */ + /* 칩·배지·삭제 버튼 겹침 방지. 주의: SessionsPanel 세션 목록의 인라인 flexWrap(전 뷰포트)이 + 존치돼 지배 규칙이 갈라져 있다(자체 적대 리뷰 P3-3) — 폰 wrap 정책을 바꿀 땐 그 인라인을 + 함께 정리할 것(JSX 무변 계약이라 이번 PR 은 CSS 만). */ + flex-wrap: wrap; } } diff --git a/src/renderer/styles.responsive.test.ts b/src/renderer/styles.responsive.test.ts index b0eafdc..4d89c52 100644 --- a/src/renderer/styles.responsive.test.ts +++ b/src/renderer/styles.responsive.test.ts @@ -151,8 +151,10 @@ describe('#221 ≤640px 셸 블록 — 폰 분기 규칙 존재 핀(미감 수 }) describe('#221 PR2 — 패널 1열 스택·칩 스트립·폼 핀(셀렉터 결합)', () => { - it('G9 .chat/.project-layout 폰 1열(232px 사이드바 해제)', () => { + it('G9 .chat/.project-layout 폰 1열(232px 사이드바 해제) + rows auto 1fr(본문 높이 계약)', () => { expect(shell).toMatch(/\.chat,\s*\.project-layout\s*\{[^}]*grid-template-columns: *1fr/) + // load-bearing 선언(암묵 auto 행 = 본문 높이 붕괴)인데 무핀이던 갭(자체 적대 리뷰 P2-1). + expect(shell).toMatch(/\.chat,\s*\.project-layout\s*\{[^}]*grid-template-rows: *auto 1fr/) }) it('.rooms 가로 칩 스트립(overflow-x auto — C2 .modal-chips 선례)', () => { From 7b96d61b36d396bf60afe8aefeed96d1f332b504 Mon Sep 17 00:00:00 2001 From: Dowon Park Date: Tue, 14 Jul 2026 00:58:54 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test(#221):=20CodeRabbit=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=E2=80=94=20.chat=20safe-area=20=EC=B0=A8=EA=B0=90?= =?UTF-8?q?=EC=8B=9D=20=ED=95=80(mutation=201=ED=9A=8C=20=EC=8B=A4?= =?UTF-8?q?=ED=9A=A8=20=EA=B2=80=EC=A6=9D=C2=B7=EC=8A=A4=EC=BA=90=ED=8F=B4?= =?UTF-8?q?=EB=94=A9=20=EB=AF=B8=EC=BB=A4=EB=B0=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CkNHx6UJ5U24PUbpnQycFp --- src/renderer/styles.responsive.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/renderer/styles.responsive.test.ts b/src/renderer/styles.responsive.test.ts index 4d89c52..d00da27 100644 --- a/src/renderer/styles.responsive.test.ts +++ b/src/renderer/styles.responsive.test.ts @@ -161,6 +161,15 @@ describe('#221 PR2 — 패널 1열 스택·칩 스트립·폼 핀(셀렉터 결 expect(shell).toMatch(/\.rooms\s*\{[^}]*overflow-x: *auto/) }) + it('.chat 높이의 safe-area 차감식 — env()=0 인 Playwright 가 못 보는 실기기 보정 핀(CodeRabbit)', () => { + const chatInShell = ruleBlocks(shell, '.chat') + const heightDecl = chatInShell.find((b) => b.includes('height: calc(')) + expect(heightDecl, '.chat 높이 calc 재선언 존재').toBeDefined() + // 상·하단 additive 초과분을 각각 max(0px, env - 기본값) 으로 차감 — env=0 이면 현행 기하 불변. + expect(heightDecl).toMatch(/max\(0px,\s*env\(safe-area-inset-top\) - 10px\)/) + expect(heightDecl).toMatch(/max\(0px,\s*env\(safe-area-inset-bottom\) - 7px\)/) + }) + it('G11 .grid-2 폰 1열(elicitation·manual 역할 배정)', () => { expect(shell).toMatch(/\.grid-2\s*\{[^}]*grid-template-columns: *1fr/) })