fix(b01): 챗봇 창 블랙 · 화이트 테마 색 맞춤 · Enter 보내기 · Shift+Enter 줄바꿈 (275-1·2)

- 없는 색 변수(--color-text-primary · --color-background) 걷고 테마 변수로 — 어두운 테마 제목 · 말풍선 글자 안 보임 해소
- 단추 · 내 말풍선 = primary 색 짝 · 답 말풍선 = 떠 있는 표면 + 테두리 · 입력칸 바탕 · 글자
- Enter = 보내기 · Shift+Enter = 줄바꿈 · 한글 조합 중 Enter 무시
- 화면 시험 ui_checks/275 추가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjUPRxjDxULvh5VA7kX5py
This commit is contained in:
2026-10-11 21:28:54 +09:00
co-authored by Claude Opus 5.5
parent 7b5ee62a42
commit 192d4feebe
3 changed files with 120 additions and 11 deletions
@@ -58,11 +58,17 @@ export function buildKnowledgeChat(): HTMLElement {
input.rows = 2;
input.required = true;
input.maxLength = 500;
input.placeholder = "메시지를 입력하세요";
input.placeholder = "메시지를 입력하세요 (Shift+Enter 줄바꿈)";
input.setAttribute("aria-label", "질문");
const submit = document.createElement("button");
submit.type = "submit";
submit.textContent = "보내기";
input.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
event.preventDefault();
submit.click();
}
});
form.append(input, submit);
panel.append(header, messages, form);
wrap.append(toggle, panel);
+17 -10
View File
@@ -536,8 +536,8 @@
.b01-knowledge-chat__toggle,
.b01-knowledge-chat__form button {
border: 0;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
background: var(--color-primary);
color: var(--color-primary-text);
font: inherit;
cursor: pointer;
}
@@ -565,7 +565,7 @@
border-radius: 12px;
background: var(--color-surface, #fff);
box-shadow: 0 8px 28px #0003;
color: var(--color-text-primary, #222);
color: var(--color-text-body);
overflow: hidden;
overscroll-behavior: contain;
}
@@ -588,8 +588,8 @@
height: 34px;
place-items: center;
border-radius: 50%;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
background: var(--color-primary);
color: var(--color-primary-text);
}
.b01-knowledge-chat__avatar svg {
@@ -631,14 +631,15 @@
.b01-knowledge-chat__user {
align-self: flex-end;
border-bottom-right-radius: 4px;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
background: var(--color-primary);
color: var(--color-primary-text);
}
.b01-knowledge-chat__reply {
align-self: flex-start;
border: 1px solid var(--color-border);
border-bottom-left-radius: 4px;
background: var(--color-background, #f4f4f4);
background: var(--color-surface-raised);
}
.b01-knowledge-chat__reply details {
@@ -722,7 +723,7 @@
.b01-knowledge-source-modal__content code {
padding: 0.1em 0.35em;
border-radius: 4px;
background: var(--color-background, #f4f4f4);
background: var(--color-surface);
}
.b01-knowledge-source-modal__content .ui-markdown-table {
@@ -746,7 +747,7 @@
}
.b01-knowledge-source-modal__content th {
background: var(--color-background, #f4f4f4);
background: var(--color-surface);
}
.b01-knowledge-chat__form {
@@ -765,11 +766,17 @@
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-canvas);
color: var(--color-text-body);
font: inherit;
resize: vertical;
overflow-y: auto;
}
.b01-knowledge-chat__form textarea::placeholder {
color: var(--color-text-muted);
}
.b01-knowledge-chat__form button {
padding: 9px 14px;
border-radius: 8px;
@@ -0,0 +1,96 @@
/* 275 · 대시보드 챗봇 — 블랙 · 화이트 테마 색 · 엔터 보내기
* 시작 화면: B01 대시보드(#/b01-account) · 로그인
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌)
* 기대 수치:
* - 밝음 · 어두움 모두 글자 대비 ≥ 4.5(제목 · 답 말풍선 · 내 말풍선 · 보내기 · 입력칸 · 열기 단추 · 머리 아이콘)
* - Enter = 보냄(내 말풍선 +1 · 입력칸 빔) · Shift+Enter · 한글 조합 중 Enter = 안 보냄
* - 서버로 안 나감(fetch 가로챔) · 끝나면 테마 · 말풍선 · fetch 되돌림
* ⚠ 대화 기록(화면 메모리)에 시험 한 줄이 남음 — 끝나면 새로고침
*/
(async () => {
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const rgb = (s) => (s.match(/[\d.]+/g) || []).map(Number);
const lum = ([r, g, b]) => {
const f = (v) => ((v /= 255) <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
};
/** 실제로 보이는 바탕 — 투명이면 조상으로 */
const bgOf = (el) => {
for (let e = el; e; e = e.parentElement) {
const c = rgb(getComputedStyle(e).backgroundColor);
if (c.length >= 3 && (c.length < 4 || c[3] > 0.5)) return c.slice(0, 3);
}
return [255, 255, 255];
};
const ratio = (a, b) => {
const [x, y] = [lum(a), lum(b)];
return +((Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05)).toFixed(2);
};
const text = (el) => ratio(rgb(getComputedStyle(el).color).slice(0, 3), bgOf(el));
const root = document.documentElement;
const startTheme = root.getAttribute("data-theme");
const chat = document.querySelector(".b01-knowledge-chat");
if (!chat) return JSON.stringify({ error: "챗봇 없음 — 대시보드에서 부를 것" });
const panel = chat.querySelector(".b01-knowledge-chat__panel");
const input = chat.querySelector("textarea");
const messages = chat.querySelector(".b01-knowledge-chat__messages");
const users = () => messages.querySelectorAll(".b01-knowledge-chat__user").length;
const wasHidden = panel.hidden;
if (wasHidden) chat.querySelector(".b01-knowledge-chat__toggle").click();
const before = messages.children.length;
const realFetch = window.fetch;
window.fetch = async (url, init) =>
String(url).endsWith("/knowledge-chat")
? new Response(JSON.stringify({ answer: "시험 답", sources: [] }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
: realFetch(url, init);
const press = (opts) => {
const event = new KeyboardEvent("keydown", {
key: "Enter",
bubbles: true,
cancelable: true,
...opts,
});
input.dispatchEvent(event);
return event.defaultPrevented;
};
const out = {};
try {
input.value = "시험 질문";
const start = users();
out.shiftEnter = { sent: press({ shiftKey: true }) || users() > start };
out.composing = { sent: press({ isComposing: true }) || users() > start };
out.enter = { handled: press({}) };
await w(300);
out.enter.userBubbles = users() - start;
out.enter.inputEmpty = input.value === "";
for (const theme of ["light", "dark"]) {
root.setAttribute("data-theme", theme);
await w(50);
const reply = messages.querySelector(".b01-knowledge-chat__reply");
out[theme] = {
title: text(panel.querySelector("header strong")),
avatar: text(chat.querySelector(".b01-knowledge-chat__avatar")),
reply: text(reply),
user: text(messages.querySelector(".b01-knowledge-chat__user")),
send: text(chat.querySelector(".b01-knowledge-chat__form button")),
input: text(input),
toggle: text(chat.querySelector(".b01-knowledge-chat__toggle")),
panelBg: getComputedStyle(panel).backgroundColor,
replyBg: getComputedStyle(reply).backgroundColor,
inputBg: getComputedStyle(input).backgroundColor,
};
}
} finally {
window.fetch = realFetch;
if (startTheme === null) root.removeAttribute("data-theme");
else root.setAttribute("data-theme", startTheme);
[...messages.children].slice(before).forEach((node) => node.remove());
if (wasHidden) chat.querySelector(".b01-knowledge-chat__close").click();
}
out.themeRestored = root.getAttribute("data-theme") === startTheme;
return JSON.stringify(out);
})();