Repository navigation
feat(chat): the model and the level in one quiet pill, with a readable name where the id has one - #467
Conversation
…e name where the id has one The agent mark, the model, the reasoning level and the context ring sit in one pill in the composer's controls row. It is display only: no role, no focus, no hover. DONE stays drawn outside it. modelLabel (src/lib/modelName.ts) names an id only when it matches a vendor's regular pattern whole: claude-<family>-<major>[-<minor>] (also behind anthropic/), gpt-<version>, glm-<version>. Every other id is drawn as received in the mono face, so no suffix is dropped and no name is guessed. When the label steps out (composerModelDraw, unchanged) the pill goes with it and the ring stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o box Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me, no pill or dash for what a pane does not record - The fit check compares the pill's parts with each other again, in every draw, and the level is one of the parts the pill must hold. - Behind a name the id as received is repeated as visually hidden text (a touch cannot reach the title). - A pane that records a level and no model draws no pill: a dim placeholder and the level stand in the row. A pane that records no level draws the name alone, no dot and no dash. - The level's first letter alone is a capital (::first-letter), so "extra high" keeps its words. - The ring's stronger track is used only on the pill's fill, not after the label stepped out. - gpt-<version>-sol is named GPT-<version>-Sol, as Codex's own status line writes it (server/prompt.test.ts); no other tier word is named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 SummarySummary by CodeRabbit
WalkthroughThe composer displays the agent mark, model, reasoning level, and context ring in a status pill. It formats recognized Claude, GPT, and GLM model IDs, while leaving other IDs unchanged. The pill’s contents and visibility respond to available space and Queue state. ChangesComposer Status Pill
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Feature Merge Risk: ⚪ Minimal · up to The model pill and labeling changes have no established merge-blocking issue in the supplied evidence. Complete normal validation before merging. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 4 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
…hown as received The pill still names a model by the vendor's id syntax, not from a list of released versions, so a new model needs no change. The syntax is now read strictly: a version part with a leading zero (claude-sonnet-5-05, gpt-05.6, glm-5.03), a major of 0, and a ".0" minor on a GPT or GLM id are not named. The header comment and a test state the accepted consequence: a well-formed id of a version nobody released is named. DESIGN.md and the changelog said the pill always steps out beside a task chip and Queue on a phone. It steps out when it does not fit: the fit check now pins a short raw id with no level drawn whole at 390px, and the shortest named label with a level stepped out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranscript (#468) ## Change 승인하신 채팅 모드 재설계(목업 `mockups/chat-mode`)의 조각 하나입니다. 앞선 조각은 #456 ~ #466으로 머지되었습니다. 이번 세 번째 묶음은 모델 표시(pill)와 이 PR, 곧 입력 카드 위에 붙는 승인 카드입니다. 마지막 묶음은 글자 크기 체계와 글꼴입니다. 이 PR은 목업 규칙 **D1**(approval dock)과 **D2**(waiting head)를 옮깁니다(`variants/d.css`, `d.js`). **토론 판정 R1-D3(카드는 대화 안에 둔다)를 뒤집습니다.** 2026-10-05에 승인하신 내용이며 첫 커밋 본문에도 적었습니다. ### 사용자가 보는 것 - 에이전트의 질문, 승인, 계획 카드가 대화 끝에 붙어 있지 않고 입력 카드 바로 위, 같은 열에 놓입니다. 위에서부터 대화, 보류 메시지(한 줄로 접힘), 승인 카드, 입력 카드 순서입니다. 대화는 카드 뒤에서 자유롭게 스크롤됩니다. - 카드는 그림자가 없고 `--border-strong` 테두리에 `--bg-elevated` 바탕입니다. 빨간색은 제목 하나뿐입니다. "INPUT NEEDED" 배지는 그리지 않지만 스크린 리더는 읽습니다. - 선택지는 숫자 키캡이 붙은 납작한 줄이며 무게가 모두 같습니다. 키캡에는 숫자만 그리고, 접근성 이름은 "1. Yes"를 유지합니다. - 카드 높이는 앱 높이의 60%와 240px(터치 줄 여섯 개) 중 큰 값까지입니다. 줄어드는 것은 참고 텍스트(코드 상자)뿐이고(여섯 줄, 최소 두 줄), 그래도 넘치면 카드 전체가 스크롤되며 Confirm은 항상 보입니다. - 메시지 상자의 안내는 "Type 1–3 to choose…" 또는 "Type 1–3 or your own reply…"입니다. 폰에서 잘리던 "Answer above:"를 뺐습니다. - 에이전트가 사용자를 기다리는 동안 열린 작업 블록은 "Working…" 대신 "Needs you"로 읽히고, 빨간 점은 움직이지 않습니다. pane 상태(blocked)로 판단합니다. - 답하는 방법은 그대로입니다. 선택지를 누르거나, 숫자를 입력하고 Confirm을 누르거나, 직접 답을 씁니다. 빈 상자에서 Enter는 아무것도 보내지 않습니다. 답 경로(`POST /api/pane/prompt/answer`)와 HTTP/WS 계약은 바뀌지 않습니다. ### 구현 `PaneTerminal`이 `.prompt-dock` 요소로 순서를 정하고, 프롬프트와 폴링, 답을 여전히 가진 `ChatView`가 포털로 카드를 그 안에 그립니다. 새 문구 4개는 ko, ja, zh에 모두 넣었습니다. 변경 이력은 Unreleased에 적었습니다. ### 목업과 다른 점 - 카드는 목업처럼 `.composer`의 자식이 아니라, 그 앞 형제인 `.prompt-dock` 안에 있습니다(보류 목록과 같은 방식). dock이 포털 대상이자 `aria-live="polite"` 영역이고, 카드는 "Message composer" 그룹 밖에 남습니다. 새 카드가 읽히도록 dock은 비어 있을 때도 높이 0으로 렌더링됩니다. - `is-command` 클래스와 질문의 고정폭 상자는 만들지 않았습니다. 서버 파서(Codex, omp, Claude 승인)가 이미 명령을 `body`에 넣고, 그것이 카드의 고정폭 상자입니다. pi의 confirm은 메시지를 `question`에 넣지만 명령임을 알리는 신호가 없습니다. 데모 fixture는 Codex의 실제 형태(title = question, body = 명령)로 바꿨습니다. - 카드와 입력 카드 사이 간격은 목업의 `--space-2`가 아니라 `--composer-grip-h`(12px, 터치 20px)입니다. 보류 목록 아래와 같은 크기 조절 손잡이 영역을 그대로 둡니다. - 열 너비는 목업의 `--content-w`가 아니라 #463의 `--chat-w`입니다. - 한 줄짜리 참고 텍스트는 두 줄이 아니라 한 줄까지 줄어듭니다. 본문 페이드 띠는 `--space-3`이 아니라 `--space-2`입니다. - 카드 글자는 대화 안에 있을 때처럼 설정의 Chat font size와 Chat font를 따릅니다(본문, 키캡, 단계 표시는 고정폭 유지). 목업에는 이 설정이 없습니다. 높이 상한은 글자 크기에 따라 커지지 않습니다. - `.terminal-stack.is-chat > .terminal-surface { overflow: hidden }`을 추가했습니다. 390x500에서 대화 스크롤 영역이 카드 윗부분을 덮어 그렸기 때문입니다. - 새 동작: 카드에서 답을 누른 뒤 포커스가 메시지 상자로 갑니다. 정밀 포인터에서만, 그리고 답이 돌아왔을 때 카드가 아직 있고 포커스가 카드 안이나 페이지 본문에 있을 때만 옮깁니다(`focusFollowsAnswer`). - 새 동작: 폰 키보드가 올라와 있을 때 카드의 글자를 탭하거나, 카드가 맨 위에 있을 때 아래로 끌면 키보드가 내려갑니다(`dismissKeyboardOn`의 `atTopOnly`). 버튼, 라벨, 입력란은 예외입니다. - 과제는 안내 문구 두 개를 지목했지만, 프롬프트 안내 네 개 모두에서 "Answer above:"를 뺐습니다. ### 리뷰하실 때 볼 곳 - **판정이 필요한 것**: 60% 상한과 본문 두 줄 하한에서 나오는 결과입니다. 800x600에서 긴 폼에 숫자를 입력하면 대화는 64px(반쯤 흐려진 한 줄)만 남습니다. 390x500에서 숫자를 입력하면 Confirm이 두 줄로 접히고 선택지는 좁은 틈으로 보입니다. 390x500에 채팅 글자 24px이면 질문과 고정된 Confirm이 카드를 채우고 선택지는 스크롤해야 보입니다. 상한을 바꾸지 않으면 고칠 수 없어 그대로 두었고, 새 스크립트의 대화 높이 하한은 48px입니다. - `overflow: hidden`은 공용 요소(`.terminal-surface`)에 채팅 렌즈일 때만 거는 새 클립입니다. 인사말과 보류 줄 회귀는 통과합니다. - dock은 항상 렌더링되는 polite 라이브 영역이라 카드 안의 변화("Submit (2)", 오류)도 읽힙니다. 실제 스크린 리더로는 확인하지 못했습니다. - 배지를 숨기고 #456에서 상태 줄의 INPUT도 숨겼으므로, 작업 블록이 없는 blocked pane은 채팅 안에서 카드와 사이드바의 INPUT만으로 상태를 알립니다. - 스크린샷 한 장(긴 폼, 1440x900, 채팅 글자 24px, Confirm 고정)에서 카드 아래 모서리에 불투명하지 않은 픽셀 32개가 있었습니다. 원인을 찾지 못했고 다른 화면에서는 나오지 않았습니다. - `.prompt-card`가 `.chat-view` 안에 있다고 가정하는 외부 스크립트나 PR은 깨집니다. 저장소 안의 `scripts/film`, `scripts/readme-media`, `site/demo`는 선택자가 그대로 맞습니다. - `docs/guide.md`의 `media/look-prompt.png`와 `site/index.html`의 정적 목업은 예전 카드를 보여 줍니다. 릴리스 때 다시 찍어야 합니다. 바뀐 fixture가 이미 새 모습을 만듭니다. - "Confirm your answer in the card above, or type another…"는 390px과, 사이드바가 열린 800px에서 여전히 두 줄입니다. 범위 밖이라 두었습니다. - 대기 중인(queued) Codex 질문은 제목이 빨갛지만 작업 블록은 "Working…"으로 남습니다. 목업대로입니다. ### 열려 있는 외부 PR과의 충돌 #436(`Composer.css`, `PaneTerminal.tsx`), #409와 #438(`ChatView.tsx`, `ChatView.css`, `PromptCard`)과 충돌이 예상됩니다. #441과 #421(사이드바)은 겹치지 않을 것으로 봅니다. ## Validation 검증 환경: Linux, herdr 0.9.3, Bun 1.4.2, Chrome(`/usr/bin/google-chrome`). `origin/main`은 #466(9153e60)이며 브랜치가 그 위에 있어 병합할 것이 없었습니다. - `bun run typecheck`: 통과 - `bun run test:unit`: 1,272건 통과, 4건 건너뜀, 실패 없음(116개 파일) - `bun run build`: 통과 - `bun run test:ui`(별도 세션 `herdr-web-ui-test`) 1회: PASS 36개 뒤 `checkAlertSound`의 `.droplet-card` 대기(`scripts/alert-sound-regression.ts:79`)에서 30초 타임아웃으로 종료 코드 1. 이 장비에서는 `origin/main`에서도 전체 순서로 돌릴 때 여기서 실패하는 것으로 알려져 있으며(CI는 통과), 이번에 `main`에서 다시 확인하지는 않았습니다. 그 앞에서 실패한 검사는 없습니다. - 그래서 `checkAlertSound`와 `checkUpdateNotice` 두 호출만 뺀 `ui-regression.ts` 임시 사본(커밋하지 않고 지움)으로 나머지를 돌렸습니다: PASS 64개, 종료 코드 0. 카드가 `.chat-view` 밖에 있고 dock의 다음 형제가 `.composer`이며 카드가 입력 카드 위에서 끝나는지 보는 새 단언이 들어간 "startup prompt appears and accepts an answer while the agent status is idle"을 포함합니다. - herdr가 필요한 다른 스크립트: `chat-browser-qa.ts` PASS 7개, `file-viewer-regression.ts` PASS 6개, `directory-groups-regression.ts` PASS 7개. 모두 종료 코드 0. - herdr 없이 도는 스크립트(모두 종료 코드 0): `prompt-dock-demo-regression.ts`(이 PR의 새 스크립트, `ci-browser.sh`에 등록) PASS 15개, `held-rows-demo-regression.ts` 10개, `composer-fit-demo-regression.ts` 5개, `chat-greeting-demo-regression.ts` 10개, `droplet-demo-regression.ts` 5개, `keyboard-viewport-demo-regression.ts` 1개, `keyboard-viewport-regression.ts` 1개, `chat-history-browser-qa.ts` 5개, `skill-activity-browser-qa.ts` 2개, `file-viewer-mobile-regression.ts` 16개, `pi-image-browser-qa.ts`("browser OK" 한 줄). 고친 테스트: `promptAnswer.test.ts`(안내 문구 기대값, `focusFollowsAnswer`), `keyboard.test.ts`(`atTopOnly`, `scrolledDown`), `workBlocks.test.ts`(`isWaitingWorkTurn`), `ui-regression.ts`(카드 위치 단언). 지우거나 느슨하게 한 테스트는 없습니다. Codex 적대적 리뷰(gpt-6.1-sol, xhigh, 읽기 전용): 지적 4건(빈 라이브 영역이 숨겨짐, 채팅 글자 설정을 잃음, 늦은 답이 포커스를 가져감, 카드의 overscroll)을 모두 확인했고, 별도 리뷰에서 나온 폰 키보드 내리기 1건과 함께 202e9da에서 고쳤습니다. **확인하지 못한 것** - 실제 iPhone/Android 기기. 키보드 내리기 검사는 합성 클릭/터치 이벤트와 가짜 `navigator.virtualKeyboard`를 씁니다. - 실제 스크린 리더. - 다중 선택, 대기 중인 Codex, fallback 메뉴, 보기 전용 pane의 카드를 그려 본 것. 같은 컴포넌트와 CSS를 씁니다. - `bun run test:integration`과 `*.contract.test.ts`. 서버와 계약은 건드리지 않았습니다. - 전체 순서 안의 `checkAlertSound`, `checkUpdateNotice`(위 설명). ## PR을 연 뒤의 변경 리뷰에서 나온 Medium 네 건과 문서 한 건을 고쳤고, 그 사이 움직인 main을 merge로 합쳤습니다(rebase, force push 없음). - 85a2cc2: 늦게 돌아온 답이 다음 프롬프트를 지우지 않습니다. 프롬프트가 나타날 때마다 카드를 새로 만들고(id는 내용 해시라서 id가 아니라 폴링이 유지하는 프롬프트 객체로 구분합니다), 사라진 카드의 답은 아무것도 바꾸지 않습니다. 채팅은 그 카드의 프롬프트만 지우고, 입력해 둔 선택은 pane과 프롬프트가 맞을 때만 지웁니다. 새 카드는 맨 위에서 열리므로 이전 카드의 스크롤 위치도 남지 않습니다. - c529e0e: Confirm 줄의 질문은 최대 두 줄만 보이고 그 안에서 스크롤됩니다. Confirm과 Cancel은 항상 한 줄에 함께 있습니다. 2,166자 라벨, 390x500, 채팅 글자 24px에서도 옵션이 가려지지 않습니다. - e5615f0: 답한 뒤 메시지 입력란으로 포커스를 넘길지는 누른 방식으로 정합니다. 탭(터치, 펜)이면 넘기지 않고, 키보드와 마우스일 때만 넘깁니다. 클릭에 pointerType이 없는 브라우저를 위해 기존의 `(pointer: coarse)` 확인도 그대로 두었습니다. 그래서 coarse 포인터 기기에서는 마우스로 눌러도 이전처럼 포커스가 넘어가지 않습니다. Composer의 Queue는 touch만 보지만 여기서는 pen도 탭으로 봅니다. - 9890fe8: DESIGN.md가 입력 상자를 유일한 카드라고 하던 문장을 고쳤고, 위 동작을 적었습니다. 데모 회귀 스크립트의 프롬프트를 pane별로 나누고, 늦은 답(같은 pane, pane 전환), 교체된 카드의 스크롤, 긴 라벨, 한 줄 버튼, 탭 케이스를 추가했습니다. 추가한 단언은 모두 고치기 전 코드에서 실패하는 것을 확인했습니다. - 256e95d: origin/main(#467) merge. 확인하지 못한 것: - 터치스크린 노트북은 흉내 낸 것입니다. Chromium은 터치가 있는 컨텍스트를 항상 coarse로 보고하므로 스크립트가 그 질의의 matchMedia만 바꿉니다. 실제 기기에서는 보지 않았습니다. - 카드를 새로 만든 뒤의 라이브 영역은 DOM으로만 확인했습니다(polite 영역 안에 카드 하나가 추가됨). 스크린 리더로 들어 보지는 않았습니다. - 같은 내용의 프롬프트가 폴링 한 번 사이에 연달아 나오면 id와 객체가 같아 구분할 수 없습니다. 서버 쪽 문제로 따로 등록되어 있고 여기서는 건드리지 않았습니다. - 중간 커밋 c529e0e, e5615f0은 따로 typecheck하지 않았습니다. 85a2cc2의 스테이징 상태와 최종 head에서만 돌렸습니다. - `bun run test:ui`, `test:integration`, contract 테스트는 돌리지 않았습니다. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…orean only Pretendard's chunks hold kana and 435 ideographs: kanji and Chinese text are drawn in the device's font. Also corrects when the model pill steps aside (#467). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reads a step larger (#473) ## Change 승인하신 채팅 모드 목업을 순서대로 올리는 작업의 마지막 묶음 중 하나입니다. 앞선 조각은 #456 ~ #468이고, 이 PR은 목업의 G 규칙(`mockups/chat-mode/variants/g.css`: 서체와 읽기 크기)만 담습니다. 다른 조각은 건드리지 않습니다. **사용자가 보는 것** - 앱이 서체를 직접 가져옵니다. 화면과 채팅은 Pretendard, 채팅과 화면의 코드는 JetBrains Mono로 그립니다. 서체가 설치되지 않은 휴대폰이나 PC에서도 같은 글자가 나옵니다. - 인터넷에서 받아 오는 것은 없습니다. 파일은 앱과 함께 사용자 PC에서 나갑니다. Pretendard는 92개 조각으로 나뉘어 있어서 페이지는 그 글에 필요한 조각만 받습니다(영어 채팅은 3개 조각과 JetBrains Mono, 한국어 UI는 9개 조각과 JetBrains Mono). 한 번 받은 조각은 서비스 워커의 기존 `/assets/` 규칙이 보관합니다. - 채팅에서 답변, 내 메시지 말풍선, 작업 중 나오는 말은 15px에 줄 높이 1.65입니다. 굵은 글씨는 semibold(600), 인라인 코드는 0.87em, 코드 블록은 13px에 줄 높이 1.6, 빈 채팅의 인사말은 22px semibold입니다. - 메시지 입력란은 마우스에서는 15px, 터치에서는 그대로 16px입니다(iOS 확대 방지). - 도구 행, 시각, 작은 라벨은 크기가 그대로입니다. - 터미널은 바뀌지 않습니다. 터미널 글꼴, **Terminal font** 설정, 격자가 그대로이고, 직접 고른 **Chat font**가 여전히 맨 앞에 옵니다. - 느린 연결에서 서체가 늦게 도착해도, 채팅 맨 끝을 보던 사람은 맨 끝에 그대로 있습니다. 입력 카드는 서체가 하나 도착할 때마다 다시 맞춥니다. **코드** - `src/fonts/`: Pretendard 스타일시트와 92개 woff2 조각(패키지 그대로), `JetBrainsMono-Regular.woff2`, `fonts.css`. `src/main.tsx`가 불러오고 Vite가 `dist/assets/`에 지문을 붙여 넣습니다. - `src/styles.css`와 `DESIGN.md`: 새 토큰 `--fs-chat`(15 / compact 14), `--fs-display`(22 / compact 21), `--lh-prose` 1.65, `--lh-code` 1.6, `--tracking-display` -0.02em, 그리고 `--font-sans`, `--font-mono` 스택. 두 파일은 함께 바꿨습니다. - `src/lib/fontFaces.ts`(새 파일): 서체 도착 횟수를 세는 모듈 저장소와 `useFacesArrived` 훅. `ChatView.tsx`는 `stickToBottom`일 때 도착마다 끝으로 스크롤하고, `Composer.tsx`는 기존 `loadingdone` 효과 대신 이 훅으로 다시 맞춥니다. - `public/sw.js`: `CACHE_NAME`을 올렸습니다. - `THIRD_PARTY_NOTICES.md`: 두 서체의 OFL 전문. - 새 `t("…")` 문구, HTTP/WS 계약 변경은 없습니다. ## Why 지금은 기기에 깔린 서체에 기대기 때문에 기기마다 글자 모양과 폭이 다릅니다. 입력 카드의 모델 라벨 맞춤(#465, #467)처럼 폭을 재는 코드는 서체가 같아야 결과가 같습니다. 그리고 채팅 본문은 14px에 좁은 줄 간격이라 긴 답변을 읽기에 빽빽했습니다. 목업에서 승인하신 크기가 15px / 1.65입니다. ## 목업과 다른 점 - 서체는 의존성으로 추가하지 않고 `src/fonts/`에 넣었습니다(기존 Nerd Font와 같은 방식). `pretendard` 패키지는 풀면 97 MB에 파일 1,826개이고, `build-remote-bundle.ts`가 `node_modules`를 원격 번들마다 복사합니다. - JetBrains Mono는 `"JetBrains Mono Web"`이라는 이름으로 등록했습니다(설치된 사본이 있으면 `local()`로 먼저 씁니다). g.css는 `"JetBrains Mono"`인데, `lib/fontFamily.ts`의 터미널 스택이 그 이름을 쓰고 있어서 같은 이름이면 xterm이 웹 서체로 칸 크기를 재기 시작합니다. `--font-mono`의 맨 앞은 여전히 `"Symbols Nerd Font Mono"`입니다. - G6(pane 제목)은 #461에서, G5(조용한 행과 상태 줄의 작은 크기)는 이미 main에 있어서 바꾸지 않았습니다. - G2의 본문 자간 -0.003em은 넣지 않았습니다(토큰이 없고 눈에 보이지 않습니다). 문단 `margin-block` 규칙도 넣지 않았습니다. 기존 아래 여백으로 같은 간격이 나옵니다. `li + li` 간격은 넣었습니다. - Markdown의 h3 ~ h6을 `--fs-md`에서 본문 크기로 올렸습니다. 그대로 두면 제목이 본문보다 작아집니다. g.css에는 없는 변경입니다. - 압축 / 중단된 턴 요약(`.chat-compact-text`)은 이전의 14px / 1.5로 고정했습니다. 완료된 뒤 접힌 말(settled narration)도 13px 그대로입니다. 크기를 올리는 대상은 본문, 말풍선, 작업 중인 말뿐입니다. - 목업에 없는 제품 코드: `src/lib/fontFaces.ts`. `font-display: swap`으로 서체가 바뀔 때 스크롤 위치와 입력 카드 맞춤이 어긋나는 것을 막습니다. ## 리뷰할 때 볼 것 - **용량**: 저장소와 릴리스, 원격 번들이 약 3.1 MB 늘어납니다(woff2 93개). `dist/`는 4.57 MB에서 7.68 MB가 됩니다. - **설정 표기**: Settings는 기본값을 "Chat font size 14px"로 보여 주는데 본문은 15px로 그려집니다. 설정은 기준 크기이고 본문은 그보다 한 단계 위이며, 설정을 바꾸면 여전히 함께 커집니다. 스테퍼가 15로 읽히길 원하시면 `lib/settings.ts`의 `CHAT_BASE_FONT`를 바꿔야 하는데, 여기서는 하지 않았습니다. - **입력란 15px 조건**: `(hover: hover) and (pointer: fine)`입니다(g.css 그대로). 트랙패드를 붙인 iPad도 여기에 들어갑니다. - **다시 그리기**: 서체가 하나 도착할 때마다 입력 카드와 열려 있는 ChatView가 한 번씩 다시 그려집니다. 한국어 페이지 첫 방문에 열 번 남짓입니다. 훅은 93개 face의 `loaded` 약속을 지켜보지만 내려받기를 시작하지는 않습니다. - **끝으로 스크롤**: 서체가 도착하면 끝에서 48px 안에 있던 사람만 끝으로 옮깁니다. resize observer가 쓰는 기존 기준과 같습니다. - **`CACHE_NAME`**: 올렸기 때문에 기기마다 기존 캐시가 활성화 때 한 번 비워집니다. - **코드 안의 한글**: JetBrains Mono에 한글이 없어 시스템 고정폭 서체로 넘어갑니다. regular 굵기만 넣었으므로 굵은 코드는 합성됩니다. - `scripts/film/capture.ts`는 여전히 `"JetBrains Mono"`라는 이름을 찾아 기존 경고를 냅니다. 이 조각 밖이라 두었습니다. ## Validation 검증 환경: Linux, herdr 0.9.3, Bun 1.4.2, Chromium. 브랜치는 `origin/main`(8378a95, #467과 #468 포함)을 병합한 상태이고 뒤처진 커밋이 없습니다. - `bun run typecheck`: 통과 - `bun run test:unit`: 1,286건 통과, 4건 건너뜀, 실패 없음 - `bun run build`: 통과 - `bun run test:ui`(별도 세션 `herdr-web-ui-test`), 첫 실행: `checkDroplet`의 "no in-app alert for the pane already open"에서 멈췄습니다(PASS 31개 뒤). - 같은 `ui-regression.ts`를 `checkAlertSound`와 `checkUpdateNotice` 두 호출만 뺀 임시 사본(커밋하지 않음)으로 다시 실행: PASS 64개, 종료 코드 0. `checkDroplet`도 통과했으므로 첫 실행의 실패는 타이밍 flake로 적습니다. 뺀 두 검사는 이 기기에서 `origin/main`도 전체 순서 안에서만 실패하는 것으로 알려진 것이고 CI에서는 통과합니다. 이 PR에서는 그 둘을 이 기기에서 실행하지 않았습니다. - CI의 나머지 브라우저 스크립트: `chat-history-browser-qa` 5/5, `file-viewer-regression` 6/6, `keyboard-viewport-regression` 1/1, `file-viewer-mobile-regression` 16/16, `droplet-demo-regression` 5/5, `chat-greeting-demo-regression` 10/10, `composer-fit-demo-regression` 8/8, `held-rows-demo-regression` 10/10, `prompt-dock-demo-regression` 22/22 - CI에 없는 스크립트: `font-swap-demo-regression`(새 파일) 3/3, `chat-browser-qa` 7/7, `skill-activity-browser-qa` 2/2, `output-browser-qa` 종료 코드 0 **테스트 변경** - `src/lib/fontFaces.test.ts`(새 파일, 4건): 불러오는 중이거나 아직 요청되지 않은 face만 따라감, 여러 번 훑어도 한 번만 따라감, 다른 face가 아직 오는 중이어도 각자 도착을 알림, 실패한 로드는 도착도 아니고 처리되지 않은 거절도 아님. - `src/lib/fontFamily.test.ts`: 번들 서체 이름, 그리고 터미널 스택이 그 이름을 쓰지 않는다는 것을 고정. - `scripts/font-swap-demo-regression.ts`(새 파일, 390px 터치, woff2 응답을 1.5초 늦춤): 끝을 보던 사람은 서체가 바뀌어도 끝에 있음, JetBrains Mono 파일이 끝내 오지 않아도 같음, 위로 올려 보던 사람은 끌려 내려가지 않음. 첫 검사는 ChatView 효과가 없으면 실패합니다(간격 18px). - `scripts/app-faces.ts`: 두 서체가 로드될 때까지 기다리는 도우미. `composer-fit`, `chat-greeting`, `held-rows`, `prompt-dock` 데모 스크립트가 측정 전에 씁니다. - 기대값을 바꾸거나 지우거나 느슨하게 한 것은 없습니다. 고정된 폭, 높이, 한 줄 검사는 서체가 로드된 상태에서 그대로 통과합니다. **눈으로 본 것**: 데모 transport의 실제 클라이언트를 1440과 390 터치, 다크와 라이트, 영어와 한국어 UI에서 봤습니다. 본문과 말풍선 15px / 24.75px, strong 600, 인라인 코드 13.05px, 코드 블록 13px / 20.8px, 도구 행 12px, 제목 14px / 600, 입력란은 마우스 15px / 24px, 터치 16px / 24px, 가로 스크롤 없음. Chat font size 20 + compact에서도 본문이 따라 커집니다(21.5px). g.css와 맞습니다. **하지 않은 것** - `bun run test:integration`, `*.contract` 테스트, `bun run build:site`. - 서비스 워커가 조각을 오프라인에서 돌려주는지는 실행해 보지 않았습니다. 바뀌지 않은 `/assets/` cache-first 규칙을 읽고 판단했습니다. - 늦게 온 고정폭 서체로 모델 라벨을 다시 맞추는 경우(Windows / Consolas)는 단위 테스트로만 덮었고, 브라우저에서 라벨 폭을 확인하지는 않았습니다. - herdr를 쓰는 브라우저 스크립트(`ui-regression` 등)는 서체 로드를 기다리지 않습니다. 폭을 한 번만 읽는 검사가 서체 교체와 겹칠 수 있습니다. CI에서 흔들리면 `appFaces`를 넣으면 됩니다. **Codex 적대적 리뷰**: gpt-6.1-sol xhigh가 끝까지 돌았고 지적은 하나였습니다(Composer가 `loadingdone`에서만 다시 맞춤). 브라우저에서 재현한 뒤 `useFacesArrived`로 고쳤고, 두 OFL 전문이 원본과 같다는 것도 확인받았습니다. ## 다른 PR과의 충돌 열려 있는 외부 PR과 겹칠 수 있습니다. #436(상태 줄: `Composer.css`, `Composer.tsx`), #409와 #438(`ChatView.css`의 `.markdown` 규칙, `ChatView.tsx`), 그리고 어느 쪽이든 `CHANGELOG.md`와 `DESIGN.md`. #441과 #421(사이드바)은 겹치지 않을 것으로 봅니다. 그 PR들은 건드리지 않았습니다. ## PR을 연 뒤의 변경 적대적 리뷰의 지적을 하나씩 확인하고 고쳤습니다(`62cf511`..`c7ed562`). - **서비스 워커 (`public/sw.js`)**: `CACHE_NAME`이 바뀌면 새 워커가 켜지는 순간 옛 캐시를 지웠고, 미리 담아 두는 파일이 없어서 그 뒤 온라인으로 한 번 더 열기 전에는 오프라인 새로고침이 브라우저 오류 화면이었습니다. 이제 켜질 때는 아무것도 지우지 않고, 서버에 닿은 탐색이 새 캐시에 셸(`/`)을 넣은 다음에 다른 이름의 캐시를 지웁니다. 옮겨 담는 방식 대신 이쪽을 고른 이유: 켜지는 시점에는 새 셸이 어느 캐시에도 없어서 "새 셸이 가리키는 파일"을 알 수 없고, 옛 캐시에는 그 페이지를 띄운 셸, 번들, 이미 받은 서체 조각이 그대로 있으며, 아무것도 복사하지 않으니 옛 빌드의 해시 파일이 첫 온라인 탐색 뒤까지 남지 않습니다. 서체 조각은 여전히 받을 때만 캐시합니다. 대가: 옛 캐시에 있던 서체 조각은 그 탐색 뒤 한 번 다시 받습니다. - 검증: `src/pwa.test.ts`에 실제 `sw.js`를 가짜 CacheStorage 위에서 돌리는 테스트 4개(고치기 전 워커에서는 2개 실패). 실제 Chrome에서도 한 번 확인했습니다(main의 v3 워커로 연 뒤 새 워커로 교체하고 서버를 내린 채 새로고침: 고치기 전 `net::ERR_FAILED`, 고친 뒤 앱이 그려짐. 다음 온라인 탐색 뒤 v4만 남고 다시 오프라인으로 열림). 이 Chrome 확인은 일회용 스크립트였고 커밋하지 않았습니다. - **압축 요약 (`ChatView.css`)**: `.chat-compact-text` 안의 h3~h6은 요약 글자 크기를 물려받고, 코드는 `--fs-xs` / `--lh-base`로 되돌렸습니다(버려진 턴의 요약도 같은 상자라 함께 적용). 빌드한 CSS로 잰 값(기본 크기): main h3 14px, 인라인 코드 12px, 코드 블록 12px/1.5 → 고치기 전 15px, 12.18px(제목 안 13.05px), 13px/1.6 → 고친 뒤 14px, 12px, 12px/1.5. 채팅 글자 크기 1.25배에서도 main과 같고, 요약 밖의 답변은 그대로입니다. - **문서 (CHANGELOG, DESIGN.md)**: "어느 기기에서나 같은 글자"를 라틴 문자와 한글로 한정했습니다. 리뷰는 한자가 전혀 없다고 했지만 조각의 `unicode-range`를 세어 보니 한글 11,172자, 히라가나 83, 가타카나 88, 한자 435자가 들어 있습니다. 그래서 "가나와 수백 자의 한자만 있고, 나머지 한자와 중국어는 기기의 서체로 그려지며 일본어 한 줄에는 두 서체가 섞인다"고 적었습니다. README, docs, site에는 같은 주장이 없었습니다. 오프라인 수정은 Fixed 항목으로 추가했습니다. - **서체 도착 횟수 (`src/lib/fontFaces.ts`)**: 서체 하나의 도착이 두 번(자기 promise, 이어서 `loadingdone`) 세어졌고 전부 실패한 묶음도 한 번으로 세어졌습니다. 서체마다 한 번만 세고 실패는 세지 않습니다. 단위 테스트 7개. - **#467 항목 (CHANGELOG)**: 모델 알약이 비켜나는 조건의 괄호를 "390px 휴대폰에서 확인"으로 고쳤습니다. 검증하지 못한 것: - 서비스 워커 교체를 Safari와 Firefox에서, 그리고 설치된 앱의 실제 업데이트 경로에서 확인하지 않았습니다. - `mobile-tabs-regression.ts`(서체 도착을 쓰는 탭 줄)와 `test:ui`, `test:integration`, contract 테스트는 공유 herdr 세션이 쓰이는 중이라 돌리지 않았습니다. CI에 맡깁니다. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bumps package.json and herdr-plugin.toml to 0.3.50 and moves the Unreleased notes under [0.3.50] - 2026-10-06, with the compare link. Adds the PR link and credit the #471 entry was missing; every other entry already carries them. Raises the remote PC bundle to 17: #470 changes the prompt answer route on the bridge, so a remote PC on bundle 16 keeps the old behaviour until its bridge is updated. Push the `remote-v17` tag and let the bundle workflow publish before running release.yml. Known limits carried into the notes: Japanese and Chinese text uses the device's fonts beside Pretendard; #469 stays open for what #470 does not cover (answers after the first committing key, identical twin prompts, screens no reader knows). Carries #422 #423 #424 #425 #426 #435 #437 #439 #446 #447 #448 #452 #454 #455 #456 #457 #458 #459 #460 #461 #462 #463 #465 #466 #467 #468 #471 #473 #474 #475 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…icks for this session only (#594) ## Change `/effort` sent from a Claude Code chat was a dead end. It was not in the chat's slash list, and typed by hand it opened Claude Code's effort slider in the pane, where no reader knew the screen. herdr reports the pane idle while the slider waits, so not even the fallback card appeared, and the level had to be set in the terminal. This adds a reader for that slider (`claude-effort` in `server/prompt.ts`), so the existing prompt card shows it, the same way #501 did for `/model`. Nothing changes on the client or on the wire. - **What becomes a card.** The slider is recognised by its hint, `←/→ to adjust · Enter to confirm · s for this session only · Esc to cancel`, with a line of level names above it, the track with one `▲` above that, and the `Effort` title. The options are the levels Claude draws (low, medium, high, xhigh, max on Opus 5.5 and Fable 5.1). `▲` is read as over the level whose name it is centred on, within 3 columns, else no card. - **A pick is for this session only.** The answer moves the slider with `←` / `→` and types `s`. It never sends Enter, which saves the level as the default for new sessions (per model, in `modelSettings`). Typing `/effort high` by hand still saves a default, as it does in the terminal: that is Claude Code's own behaviour for a level typed after the command. - **Same guards as the model list.** The slider is read again before the `s`, also when the pick needs no move, and the letter goes only while `▲` stands over the chosen level. Each move after the first waits for the slider to show the one before it. A slider the reader cannot take, for example level names a narrow pane wrapped, gets no fallback card and holds pending delivery back (`modelListWaits`), because both end in Enter. - **Moves.** `←` / `→` count as moves for this responder only. Elsewhere they stay a menu's own keys (Claude's question tabs). - **One card wherever `▲` is.** The card's id does not depend on where the slider stands, so moving it in the terminal does not replace the card. - `/effort` joins Claude's built-in slash list, described in Claude Code's own words. - Ultracode (the slider's Tab toggle) is left to the terminal. Related: - #523 by @suho-han, closed today, read Claude's effort slider as part of a composer control for model and effort. Its close comment said the slider reading would feed into the maintainer's own change, with credit. This PR is written fresh against Claude Code 2.1.294's screen (#523 could not capture a real `/effort` screen), but it is the same feature, so the changelog credits it. - #501 and #503: the `/model` cards this follows (letter `s`, never Enter, look before the letter). - #456 and #467: the model and effort shown in the composer's pill. The pill picks the new level up with Claude's next answer, as it does for a model. Known limits: - #542 applies here too. The guard reads this slider's hint with `claudeModelListWaits`, so a hint split inside a word, or over more than six lines, is missed in the same way. - #469 applies here too. The slider closing between the last read and the `s` still lets the letter into Claude's prompt. - A narrow pane's slider was not captured live. A wrapped line of levels is left to the terminal, and that path is covered by unit fixtures only. - Models with four levels (Opus 4.6, Sonnet 4.6) were not captured. The reader takes whatever level names are drawn, but `▲`'s spacing there is unverified. ## Validation - `bun run typecheck`: passed. `bun run test:unit`: 1780 passed, 0 failed (before the rebase on 6993563). After the rebase: typecheck passed and `server/prompt.test.ts` 188 passed, 0 failed. - `server/prompt.test.ts`: the fixture drawing is checked against a capture of Claude Code 2.1.294's slider at 120 columns. Tests cover reading, one id across `▲` positions, keys (`←` / `→` then `s`), no card and no hold once the slider is answered or under later output, no card without the session-only key, a wrapped level line (no card, still holds), `▲` between two levels, and two route cases: a pick two levels left (`left, left, s`), and `▲` moved in the terminal mid-pick (one arrow, no letter, 409). - Real Claude Code 2.1.294 in an isolated herdr 0.9.3 session (`herdr-web-ui-test-effortprobe`), through this branch's `createServer`. `GET /api/pane/commands` lists `effort`. `/effort` then `GET /api/pane/prompt` gave the card with five levels. Answering xhigh and then low over `POST /api/pane/prompt/answer` printed `Set effort level to xhigh (this session only)` and `Set effort level to low (this session only)`, and the banner read `with low effort`. `~/.claude/settings.json` kept its modification time through every probe and pick.
Change
승인하신 채팅 모드 목업(
final-working-dark.html등)을 순서대로 올리는 작업의 한 조각입니다. 앞선 조각은 #456 ~ #466으로main에 들어가 있습니다. 이번 차례는 두 조각으로, 이 PR(모델 pill)과 입력 카드에 붙는 승인 카드(다른 PR)입니다. 마지막으로 글자 크기 단계와 글꼴이 남습니다. 그래서 이 PR만으로는 최종 목업과 똑같아 보이지 않습니다.싣는 목업 규칙은
h.css/h.js(입력창의 모델 표시)의 공통 규칙(모델과 effort가 한 덩어리, effort는 가운뎃점 뒤),mono(이름이 없는 id는 식별자 글꼴),name(읽히는 이름),pill(마크와 함께 조용한 chip 하나)입니다.사용자가 보는 것
--bg-hover바탕,--radius-pill, 컨트롤 높이)에 모입니다. DONE은 pill 밖, pill 앞에 그대로 그려집니다.claude-opus-5-5→ Opus 5.5,claude-sonnet-5→ Sonnet 5,gpt-5.6→ GPT-5.6,gpt-5.6-sol→ GPT-5.6-Sol,glm-5.3→ GLM-5.3. 그 밖의 id(날짜가 붙은 스냅숏,gpt-5.6-sol-max같은 접미사, 다른 provider 접두사, 다른 벤더)는 받은 그대로 mono 글꼴로 그립니다. 이름을 추측하지 않습니다.--text-dim가운뎃점 뒤에 첫 글자만 대문자로 나옵니다(High, Extra high). 글자 자체는 그대로이고 대문자는 CSS입니다.main은 대시를 그렸습니다). 단계는 있는데 모델이 없는 pane은 pill 없이 줄에 흐린 "Model — · High"만 그립니다.새 번역 문구는 없습니다. HTTP/WS 계약과 서버는 바뀌지 않습니다.
.conn-live, 사이드바의 READY / RUN / INPUT, Queue와 "Send now", 프롬프트 응답 경로는 건드리지 않았습니다.composerModelDraw와fitStatus도 그대로입니다. 바뀐 파일은 8개이고site/,server/,shared/아래에는 없습니다. 변경 이력은 Unreleased → Changed에 적었습니다.목업과 다른 점
gpt-5.6-sol은 GPT-5.6-Sol(하이픈)로 그립니다. 목업h.js는 "GPT-5.6 Sol"입니다. 이 tier 단어의 출처가 저장소에는 Codex 상태 줄 fixture "GPT-6-Sol"(server/prompt.test.ts)뿐이라 그 표기를 따랐습니다. 목업의 공백으로 바꾸는 것은src/lib/modelName.ts한 줄과 테스트입니다.glm-5.3은 GLM-5.3(벤더가 쓰는 하이픈 표기)입니다.h.js는 "GLM 5.3"입니다.claude-(opus|sonnet|haiku|fable|mythos)-N[-M], 같은 것의anthropic/접두사,gpt-N[.M],gpt-N[.M]-sol,glm-N[.M]. "Claude"라는 단어를 빼는 것은 Claude Code 상태 줄 "[Opus 5.5 (1M context)]"(server/prompt.test.ts)을 따랐습니다.claude-haiku-4-5-20251001). 접미사가 가려지는 일이 없게 하려는 것입니다.--fs-xs그대로입니다(h.css는--fs-sm). 크기는 마지막 조각의 몫입니다.--chip-h입니다. 컨트롤 높이로 두면 카드가 8px 커지고, 기존 폭 맞춤 검사가 이를 막습니다.--border-strong입니다. charcoal dark와 Catppuccin Mocha에서--border가--bg-hover와 같은 값이어서입니다. 카드 위에 혼자 남은 링은--border그대로입니다.--space-1여백으로 그립니다(전에는 마크 뒤였고, 마크가 이제 pill 안에 있습니다).src/AGENTS.md의 Chat composer 줄에lib/modelName.ts와 "이름을 추측하지 않는다"는 규칙을 적었습니다.결정이 필요한 것
main은 이 자리에서 짧은 모델 표시를 그립니다. 마이크를 켠 800px도 같습니다. 선택지: 지금처럼 문서화한 대로 둔다 / 좁은 카드에서는 물러나기 전에 pill의 바탕과 padding을 먼저 뺀다 / Queue가 보일 때 단계가 먼저 물러나게 한다(#465의 규칙을 뒤집음).DESIGN.md에 적고 스크립트로 고정해 두었습니다.리뷰할 때 볼 곳과 위험
main보다 일찍 물러납니다(긴 id, 1024px, 초안이 있을 때 full → out).innerText가::first-letter의text-transform을 반영한다는 데 기대고 있습니다.origin/main도 같은 상태에서.composer-status[data-hint] > .composer-status-meta { overflow: hidden }으로 자르고 있어 이 PR이 만든 것은 아닙니다.origin/main병합 커밋(feat(chat): held messages are quiet rows on the message box's column #466)과 후속 커밋이 있습니다. squash 머지에는 문제가 없습니다.다른 PR과의 충돌
외부 PR #436(상태 줄)은
Composer.tsx,Composer.css에서 충돌할 것으로 봅니다. #409, #438(채팅)도 같은 파일이나CHANGELOG.md,DESIGN.md에서 충돌할 수 있습니다. #441, #421(사이드바)은 충돌하지 않을 것으로 봅니다. 이 PR들은 건드리지 않았습니다. 같은 차례의 승인 카드 조각과는CHANGELOG.md,DESIGN.md,Composer.css에서 겹칠 수 있습니다.Validation
검증 환경: Linux, herdr 0.9.3, Bun 1.4.2, Chrome(
/usr/bin/google-chrome). 브라우저 검사는 별도 세션herdr-web-ui-test에서만 돌렸습니다. 아래는451c9dd기준이고, 이때origin/main은 #466(9153e60)이며 브랜치에 이미 병합되어 있습니다.bun run typecheck: 통과bun run test:unit: 1,269건 통과, 4건 건너뜀, 실패 없음.src/lib/modelName.test.ts가 새로 들어갔습니다(이름이 붙는 id, 받은 그대로 돌아와야 하는 id).bun run build: 통과scripts/ui-regression.ts전체 순서(test:ui의 스크립트, 위 빌드로 1회): PASS 36개 뒤checkAlertSound의.droplet-card대기(scripts/alert-sound-regression.ts:79)에서 중단, 종료 코드 1. 이 실패는 이 기기에서origin/main도 전체 순서 안에서만 똑같이 내는 것으로 알려진 것이고, 이 PR이 만든 것이 아닙니다. 그 앞의 검사는 모두 통과했고checkComposerReconnect도 그 안에 있습니다.ui-regression.ts에서 이 기기의 알려진 실패 두 개(checkAlertSound,checkUpdateNotice: 둘 다origin/main에서도 전체 순서 안에서만 실패하고 CI는 통과)만 뺀 임시 복사본(커밋하지 않고 지웠습니다): PASS 64개, 종료 코드 0.bun scripts/composer-fit-demo-regression.ts: PASS 8개. 이 PR이 바꾼 스크립트입니다: pill과 그 안의 마크, 이름, 단계, 링을 모든 경우에 쌍으로 겹침 검사, 이름 붙이기(gpt-5.6-sol→ GPT-5.6-Sol,gpt-5.6-sol-max는 그대로), 가려진 id가 이름 뒤에만 있음,extra high→ Extra high, 단계 없음, 모델 없음, 390 / 800 / 1024 / 1440 표(짧은 이름과 긴 id, 마이크 유무, 컨텍스트 숫자 닫힘과 열림), 폰에서 Queue가 보이는 동안 물러났다가 초안을 지우면 돌아옴.bun scripts/chat-browser-qa.ts: PASS 7개(getByText로 찾는 테스트 모델 id와xhigh가 그대로 맞습니다).chat-history-browser-qa.tsPASS 5개,chat-greeting-demo-regression.tsPASS 10개,held-rows-demo-regression.tsPASS 10개,droplet-demo-regression.tsPASS 5개,keyboard-viewport-regression.tsPASS 1개,file-viewer-regression.tsPASS 6개,file-viewer-mobile-regression.tsPASS 16개.돌리지 않은 것:
checkAlertSound,checkUpdateNotice는 위 이유로 이 기기에서 통과를 확인하지 못했습니다. CI가 돌립니다.bun run test:integration과*.contract.test.ts.scripts/machines-browser-qa.ts(SSH fixture 필요)..composer-status[data-status]만 읽고, 이 속성은 바뀌지 않았습니다.scripts/film/capture.ts.textContent의/RUN|DONE/만 읽습니다.그 밖에 리뷰어가 커밋되지 않은 하네스로 24개 상태를 그려 확인했습니다: 390 터치 dark와 light, Queue가 있는 초안, 긴 id, 단계 없음, 모델 없음, 두 단어 단계, 390과 1440의 DONE, 따로 한 줄을 쓰는 업로드 문장, light의 amber / report / charcoal / catppuccin, dark charcoal. 이것은 리뷰 수정(
451c9dd) 전의 트리에서였습니다. 수정 뒤에 만든 사람이 다시 본 것은 dark amber뿐입니다(GPT-5.6-Sol · Xhigh, Opus 5.5 · Extra high, 단계 없음 1440과 390 터치, 모델 없음). light와 다른 팔레트는 수정 뒤에 다시 찍지 않았습니다. 두 단어 단계와 모델 없음 상태는 실제 에이전트가 아니라 스크립트의 fixture 패치로 만들었습니다.Codex 적대적 리뷰(gpt-6.1-sol, xhigh, 읽기 전용): 4건 보고, 3건을 고쳤고(느슨해진 겹침 검사, hover로만 닿던 원래 id, 단계만 있을 때의 "Model —" pill) 1건(focus 링 잘림)은
origin/main에도 있는 것이라 뺐습니다.PR을 연 뒤의 변경
d79e335). 이름은 계속 벤더의 정규 id 문법으로 붙입니다. 출시된 버전의 목록이 아니므로 새 모델이 나와도 코드를 고칠 필요가 없고, 그 대가로 모양만 맞고 실제로는 없는 버전의 id(claude-opus-9-9,gpt-10)에도 이름이 붙습니다. 이 점은src/lib/modelName.ts의 머리 주석에 적었고, 누가 실수로 "고치지" 않도록 테스트 하나로 고정했습니다.claude-sonnet-5-05,gpt-05.6,glm-5.03)와 major가 0인 id가 그렇습니다. minor 0은 Anthropic만 쓰는 형태라claude-opus-4-0은 Opus 4.0으로 이름이 붙고,gpt-5.0과glm-5.0은 그대로 나옵니다. 이 minor 0의 구분은 결정문에 없던 것을 제가 정한 것이니 확인이 필요합니다.DESIGN.md와 CHANGELOG의 문장을 고쳤습니다. 폰에서 작업 칩과 Queue 옆의 pill이 "항상" 물러난다고 썼지만 사실이 아니었습니다. pill은 들어가지 않을 때 물러나고, 390px 폰에서 작업 칩, 링, Queue가 함께 있으면 단계가 있는 이름 붙은 모델은 모두 그 경우입니다. 단계가 없는 짧은 id는 그대로 그려집니다. 두 문서의 "그대로 나오는 id" 목록에 정규 형태가 아닌 표기도 넣었습니다.scripts/composer-fit-demo-regression.ts에 390px 경우 두 개를 넣었습니다. 작업 칩, 링, Queue가 있을 때 단계 없는o3는 온전히 그려지고, 가장 짧은 이름인gpt-6+low는 물러납니다.bun run typecheck통과,bun run test:unit1,272건 통과 / 4건 건너뜀 / 실패 없음,bun run build통과,bun run scripts/composer-fit-demo-regression.tsPASS 8개(종료 코드 0).bun run test:ui,test:integration,*.contract.test.ts는 이 커밋에서 돌리지 않았습니다. "단계가 있는 이름 붙은 모델은 모두 물러난다"는 문장은gpt-6+low,gpt-5.6+xhigh,claude-opus-5-5+xhigh로만 쟀고, 그보다 짧은 단계 낱말은 재지 않았습니다. 실제 기기에서는 보지 않았습니다(Chromium 에뮬레이션).🤖 Generated with Claude Code