바이브 코더들에게: AI에게 앱 내 에러 콘솔을 추가하라고 시키세요
Hey vibe coders: tell your AI to add an in-app error console so your app shows what broke.
핵심 요약
AI가 생성한 앱에서 발생하는 오류를 즉시 확인하고 복사할 수 있는 인앱 에러 콘솔 구현 코드 공유.
- 에러 콘솔 구현 — 앱 내에서 자바스크립트 오류를 자동으로 띄워주는 패널 추가
- 디버깅 효율화 — DevTools를 열지 않고도 에러 메시지와 스택 트레이스를 바로 복사 가능
- 바이브 코딩 팁 — AI에게 특정 기능을 요청하여 개발 생산성을 높이는 방법 제시
- 보안 주의사항 — 프로덕션 환경에서는 해당 기능을 비활성화하거나 제거할 것
바이브 코더 여기 있음.
디버그 콘솔에 대해 이야기하면서 DevTools 에러를 AI에 다시 복사해 넣는 사람들을 봤음. 이건 내가 요즘 바이브 코딩으로 만든 앱에 추가하고 있는 아주 작은 범용 버전임.
AI에게 이렇게 말하셈:
“앱에서 에러가 발생하거나 처리되지 않은 프로미스 거부(unhandled promise rejection)가 있을 때 자동으로 열리는 인앱 자바스크립트 에러 콘솔을 추가해 줘. 에러 메시지, 파일/소스, 줄 번호, 열 번호, 스택 트레이스를 캡처해야 해. 페이지 내에 눈에 띄는 패널로 보여주고, 에러 복사, 지우기, 닫기 버튼을 포함해.”
복사/붙여넣기용 버전은 여기 있음 (AI에게 포맷을 맞춰달라고 해야 할 수도 있음; 난 모바일에서 작업했음):
(() => {
const appErrors = [];
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, ch => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
}[ch]));
}
function ensureErrorConsole() {
let panel = document.getElementById("vibe-error-console");
if (panel) return panel;
panel = document.createElement("div");
panel.id = "vibe-error-console";
panel.innerHTML = `
<div class="vibe-error-head">
<strong>Vibe Coder Error Console</strong>
<div class="vibe-error-actions">
<button id="vibe-copy-errors">Copy Errors</button>
<button id="vibe-clear-errors">Clear</button>
<button id="vibe-close-errors">Close</button>
</div>
</div>
<div id="vibe-error-list"></div>
<div class="vibe-error-note">
This opens automatically when JavaScript throws. Copy the error and paste it back into your AI.
</div>
`;
const style = document.createElement("style");
style.textContent = `
#vibe-error-console {
position: fixed;
right: 18px;
bottom: 18px;
width: min(760px, calc(100vw - 36px));
max-height: min(560px, calc(100vh - 36px));
background: rgba(10, 11, 16, 0.97);
color: #f7efdf;
border: 1px solid rgba(255, 127, 145, 0.55);
border-radius: 16px;
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.72);
z-index: 999999;
overflow: hidden;
font-family: system-ui, sans-serif;
display: none;
}
#vibe-error-console.vibe-show {
display: block;
}
.vibe-error-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
padding: 12px;
border-bottom: 1px solid #444;
background: #2a1118;
}
.vibe-error-head strong {
color: #ffd8de;
font-size: 13px;
}
.vibe-error-actions {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.vibe-error-actions button {
border: 1px solid #555;
background: #1d1f2a;
color: #f7efdf;
border-radius: 8px;
padding: 6px 8px;
cursor: pointer;
}
#vibe-error-list {
padding: 12px;
overflow: auto;
max-height: 420px;
}
.vibe-error-item {
border: 1px solid #444;
background: #111;
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
}
.vibe-error-item b {
color: #ffd8de;
}
.vibe-error-meta {
color: #aaa;
font-size: 12px;
margin-top: 4px;
}
.vibe-error-item pre {
white-space: pre-wrap;
word-break: break-word;
max-height: 180px;
overflow: auto;
margin: 8px 0 0;
color: #f7efdf;
}
.vibe-error-note {
color: #aaa;
font-size: 12px;
padding: 0 12px 12px;
line-height: 1.35;
}
`;
document.head.appendChild(style);
document.body.appendChild(panel);
document.getElementById("vibe-close-errors").onclick = () => {
panel.classList.remove("vibe-show");
};
document.getElementById("vibe-clear-errors").onclick = () => {
appErrors.length = 0;
document.getElementById("vibe-error-list").innerHTML = "";
};
document.getElementById("vibe-copy-errors").onclick = async () => {
const text = appErrors.map((e, i) => (
`#${i + 1} ${e.time}\n` +
`${e.message}\n` +
`${e.source} ${e.line}:${e.col}\n` +
`${e.stack}`
)).join("\n\n");
await navigator.clipboard.writeText(text || "No captured errors.");
};
return panel;
}
function openErrorConsole(payload) {
const item = {
time: new Date().toLocaleTimeString(),
message: payload.message || String(payload.error || "Unknown error"),
source: payload.source || "",
line: payload.lineno || payload.line || "",
col: payload.colno || payload.col || "",
stack: payload.error?.stack || payload.stack || ""
};
appErrors.push(item);
const panel = ensureErrorConsole();
const list = document.getElementById("vibe-error-list");
const div = document.createElement("div");
div.className = "vibe-error-item";
div.innerHTML = `
<b>${escapeHtml(item.message)}</b>
<div class="vibe-error-meta">
${escapeHtml(item.source || "inline/runtime")}
${item.line ? " line " + escapeHtml(item.line) : ""}
${item.col ? ":" + escapeHtml(item.col) : ""}
· ${escapeHtml(item.time)}
</div>
<pre>${escapeHtml(item.stack || item.message)}</pre>
`;
list.prepend(div);
panel.classList.add("vibe-show");
}
window.addEventListener("error", event => {
openErrorConsole(event);
});
window.addEventListener("unhandledrejection", event => {
openErrorConsole({
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack || ""
});
});
})();

