
AI로 요구사항을 클릭형 프로토타입으로 만드는 방법
카드와 버튼이 멋져도 예약 확정으로 아무것도 변하지 않으면 흐름을 검토할 수 없습니다. 인터랙티브 프로토타입에는 화면뿐 아니라 동작이 필요합니다.
원형에서 검증할 한 가지 결정 정하기
가상 예약 예는 오전·오후 각 두 자리입니다. 확정은 한 자리를 쓰고, 재확정은 추가 차감하지 않으며, 취소는 원래 시간대에 한 자리를 돌려줍니다.
페이지 메모리만 쓰므로 새로고침은 초기화하고 다른 탭과 잔여석을 공유하지 않습니다. 검토 범위이지 숨은 운영 예약 서버가 아닙니다.
| 상태 | 행동 | 기댓값 |
|---|---|---|
| 미선택 | 확정 시도 | 불가, 잔여 2/2 |
| 오전 선택 | 확정 | 오전 1, 오전 예약 |
| 예약됨 | 재확정 | 추가 차감 없음 |
| 예약됨 | 취소 | 오전 2, 예약 초기화 |
| 예약 없음 | 재취소 | 잔여 불변 |
| 오전 마감 | Morning 다음 Afternoon 선택 | 오전은 0이며 예약 불가, 오후는 예약 가능 |
| 시나리오 변경 | 초기 잔여석 변경 | 예약과 선택을 지우고 선택한 시나리오로 초기화 |
“직관적으로 만들라”보다 작성자와 검토자가 같은 성공 기준을 갖습니다.
AI에 행동과 범위 제공하기
단일 HTML 예약 프로토타입을 만드세요.
기본 label/select/button과 보이는 상태 문구를 쓰세요.
오전·오후 각 2자리, 페이지당 예약 하나입니다.
미선택은 확정 불가, 선택 시간대에 자리가 있을 때만 확정합니다.
예약 중 재확정·선택 변경을 막으세요.
취소는 예약 시간대에 정확히 한 자리만 복원하고 예약을 지웁니다.
반복 취소는 무효입니다. polite live region으로 갱신을 알리세요.
외부 라이브러리·통신·개인정보·결제·영속화는 없습니다.
완전한 HTML·열기 방법·인수 사례를 반환하세요.
동시 예약이나 운영 보장을 주장하지 마세요.상태 표와 프롬프트를 보내 완전한 HTML 파일을 받습니다. 파일을 열어 아래 다섯 검수 사례를 직접 실행하며 스크린샷이나 “구현됨”이라는 답변으로 대신하지 않습니다.
EvoLink 요청부터 검토할 파일까지
- EvoLink Gemini 4 페이지의 Agent 또는 API 연결 안내를 따라 상태 표와 프롬프트를 함께 전달합니다. 일반 시나리오는 잔여석 2/2, 오전 마감은 0/2이며, 시나리오를 바꾸면 선택과 예약을 초기화하도록 추가합니다. 읽기 쉬운 배치, 잔여석, 예약·취소·마감 안내도 요청합니다.
- 스타일과 스크립트를 포함한 완전한 HTML 문서를 요청합니다. Agent에는
booking.html로 저장하도록 하고, API 클라이언트에서는 반환된 HTML 본문에서 Markdown 코드 울타리를 제거한 뒤 저장합니다. JSON 응답 전체를 HTML로 저장하지 마세요. 열기 전에 예상하지 못한 외부 스크립트나 네트워크 요청이 있는지 확인합니다. - 브라우저에서
booking.html을 엽니다. 일반 시나리오에서 Morning을 선택하고 확정하면 잔여석이 2에서 1로 줄고 예약 안내가 표시되어야 합니다. 취소하면 2로 돌아오고 선택이 해제됩니다. 테스트에서 취소 처리 함수를 다시 호출해도 2를 유지해야 합니다. - Morning full로 바꾸고 Morning을 선택합니다. 잔여석 0, 마감 안내, 확정 버튼 비활성화를 확인합니다. Afternoon은 예약할 수 있으며 확정하면 2에서 1로 줄어듭니다. Normal로 돌아가면 예약이 지워지고 2/2로 초기화됩니다. 이 기능은 테스트 데이터를 설정할 뿐 다른 사용자나 서버 동시 처리를 재현하지 않습니다.
- 실패하면 초기 상태, 클릭 순서, 예상 결과, 실제 결과와 HTML을 같은 연결 경로로 전달하고 해당 문제의 수정을 요청합니다. 이후 두 시나리오를 다시 확인합니다. 게이트웨이를 통해 다른 모델을 비교하기 전에 승인한 결과와 사용량을 기록하세요. 파일 생성만으로 품질이나 비용 우위를 입증할 수는 없습니다.
실행 가능한 전체 참고 HTML
booking.html로 저장해 브라우저에서 엽니다.아래 코드는 직접 작성한 교육용 참고 예제이며 모델의 실측 응답이 아닙니다. 실행 파일을 동일하게 유지하기 위해 모든 언어판의 예제 화면은 영어입니다. 배치와 상태 안내는 흐름을 검토하기 위한 것으로 최종 제품 디자인은 아닙니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Session booking · Review prototype</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; background: #f4f6f8; color: #172033; font: 16px/1.6 system-ui, sans-serif; }
main { max-width: 640px; margin: 48px auto; padding: 28px; background: white; border: 1px solid #dce2ea; border-radius: 16px; }
h1 { margin: 8px 0; font-size: 30px; line-height: 1.2; }
p { margin: 12px 0; }
.eyebrow { color: #526077; font-size: 13px; text-transform: uppercase; letter-spacing: .08em; }
label { display: block; font-weight: 600; margin: 16px 0 6px; }
select, button { font: inherit; padding: 10px 14px; border: 1px solid #aeb9c8; border-radius: 8px; }
select { width: 100%; background: white; color: #172033; }
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
button { cursor: pointer; background: white; color: #172033; }
#confirm { background: #193e87; color: white; border-color: #193e87; }
button:disabled { opacity: .5; cursor: not-allowed; }
:focus-visible { outline: 3px solid #b56b00; outline-offset: 3px; }
#status { background: #edf3fc; padding: 16px; border-radius: 8px; }
small { display: block; color: #526077; }
fieldset { margin: 28px 0 0; padding: 16px; border: 1px dashed #aeb9c8; border-radius: 8px; }
legend { font-weight: 600; }
@media (max-width: 680px) { main { margin: 16px; padding: 20px; } }
</style>
</head>
<body><main>
<p class="eyebrow">Interactive prototype · Local demo</p>
<h1>Book a workshop session</h1>
<p>Choose a session, confirm your place, or cancel to release it.</p>
<p id="capacity"></p>
<label for="slot">Session</label>
<select id="slot"><option value="">Choose a session</option>
<option value="am">Morning</option><option value="pm">Afternoon</option></select>
<div class="actions">
<button id="confirm" type="button" disabled>Confirm booking</button>
<button id="cancel" type="button" disabled>Cancel booking</button>
</div>
<p id="status" role="status" aria-live="polite"></p>
<small>One booking per page. Refresh resets this demo; other tabs do not share availability.</small>
<fieldset><legend>Review scenarios</legend>
<label for="scenario">Starting availability</label>
<select id="scenario"><option value="normal">Normal · 2 morning / 2 afternoon</option>
<option value="full">Morning full · 0 morning / 2 afternoon</option></select>
<p><small>Changing the scenario clears the current booking and selection. This control is for review only.</small></p>
</fieldset>
</main>
<script>
const capacity = { am: 2, pm: 2 };
const names = { am: "Morning", pm: "Afternoon" };
let selected = "";
let booking = null;
const slot = document.getElementById("slot");
const scenario = document.getElementById("scenario");
const confirmButton = document.getElementById("confirm");
const cancelButton = document.getElementById("cancel");
const status = document.getElementById("status");
const availability = document.getElementById("capacity");
function render(message = "") {
const valid = Object.hasOwn(capacity, selected);
confirmButton.disabled = !valid || booking !== null || capacity[selected] <= 0;
cancelButton.disabled = booking === null;
slot.disabled = booking !== null;
availability.textContent = `Available: morning ${capacity.am}, afternoon ${capacity.pm}`;
status.textContent = message || (booking !== null
? `Booked: ${names[booking]}. Your place is reserved in this demo.`
: valid && capacity[selected] === 0
? `${names[selected]} is full. Choose another session.`
: "Choose a session to book your place.");
}
slot.addEventListener("change", () => { selected = slot.value; render(); });
confirmButton.addEventListener("click", () => {
if (!Object.hasOwn(capacity, selected) || booking !== null || capacity[selected] <= 0) return;
capacity[selected] -= 1;
booking = selected;
render();
});
cancelButton.addEventListener("click", () => {
if (booking === null) return;
const cancelled = names[booking];
capacity[booking] += 1;
booking = null;
selected = "";
slot.value = "";
render(`${cancelled} booking cancelled. Your place has been released.`);
});
scenario.addEventListener("change", () => {
capacity.am = scenario.value === "full" ? 0 : 2;
capacity.pm = 2;
booking = null;
selected = "";
slot.value = "";
render("Review scenario reset. Choose a session to test it.");
});
render();
</script>
</body></html>디자인보다 행동 먼저 검사하기
미선택에서 확정이 비활성인지 보고 오전 선택·확정을 거쳐 예약 표시를 확인합니다. 취소 후 각 두 자리인지, 반복 취소로 세 자리가 되지 않는지 UI나 자동 검사로 봅니다.
다른 시간대도 시험합니다. 기존 예약을 지운 뒤 선택할 수 있어야 합니다. 버튼 비활성 외에 처리 함수 조건도 필요해 코드가 재호출되어도 상태를 지켜야 합니다.
이제 Review scenarios에서 마감 분기를 직접 확인할 수 있습니다. 네트워크 오류와 요청 처리 중 상태는 이 로컬 예제의 범위 밖입니다. 실제 서비스에 연결하기 전에 따로 정의하세요.
코드뿐 아니라 요구 공백 찾기
| 관찰 | 정할 질문 |
|---|---|
| 확정 후 시간대 변경 | 취소 후 재예약인가 별도 변경인가 |
| 두 사람이 마지막 자리 예약 | 어느 서버가 용량·충돌을 관리하나 |
| 새로고침으로 사라짐 | 영속화가 이번 범위인가 |
| 키보드 사용 불가 | 라벨·초점 순서·피드백 충분한가 |
다음 구현에서는 실제 잔여석과 충돌을 백엔드가 관리해야 합니다. 로컬 카운터로 동시 예약을 입증할 수 없습니다.
Next.js 앱에서는 저장소 컴포넌트와 상태 경계를 재사용하고 독립 HTML을 그대로 운영에 넣지 않습니다. 파일은 제품 결정을 구체화하는 검토 자료입니다.
요구와 작동 파일 함께 전달하기
요구 버전·원형·인수 표·관측·미해결 제품 질문을 보관합니다. 취소 규칙이 바뀌면 상태 표와 UI를 같이 고치고 영향을 받은 사례를 재검사합니다.
자주 묻는 질문
클릭형 원형에 백엔드가 필수인가요?
로컬 흐름에는 아니지만 공유 예약과 영속 용량은 권위 있는 구현이 필요합니다.
디자인 외에 무엇을 주나요?
상태·행동·기댓값·실패·범위입니다. 그림은 전환 규칙을 정의하지 않습니다.
버튼 비활성만 충분한가요?
처리 함수도 조건을 확인해 UI만이 유효성 장치가 되지 않게 합니다.
취소를 두 번 시험하는 이유는요?
한 예약에 여러 번 자리를 돌려주는 버그를 찾습니다.
그대로 운영에 쓸 수 있나요?
앱 구조에 맞추고 백엔드·검증·접근성 요구를 구현해야 합니다.
검토 준비가 된 기준은요?
파일이 열리고 약속한 동작이 작동하며 한계가 전달되고 검토자가 사례를 재현할 수 있어야 합니다.


