Design by Story은(는) 무엇을 하나요?
여러 기기에서 볼 수 있는 단일 파일 HTML 프로토타입을 제품의 사용자 스토리별로 기기별 구분과 동일한 방식으로 분류하여 만듭니다.
상단 바에는 두 개의 전환기가 있습니다.
- 기기: 예: 웹사이트 · Android · iPhone. 동일한 제품이지만 프레임이 다릅니다.
- 스토리: "모든 스토리"와 각 사용자 스토리별 항목. 스토리를 선택하면 공유 데이터가 해당 스토리의 초기값으로 재설정되고, 모든 기기가 스토리의 시작 화면으로 이동하며, 스토리, 우선순위, 상태, 수락 기준, 연습 과정 및 메모가 포함된 패널이 열립니다.
두 선택 항목 모두 URL 해시(#story=S2&device=iphone)에 저장되므로, 특정 스토리의 특정 기기 링크를 리뷰나 티켓에 복사하여 붙여넣을 수 있습니다.
워크플로우
-
입력 수집 (한 번의 메시지로 누락된 정보만 질문):
- 제품이 무엇이며 누가 사용하는지.
- 표시할 기기. 기본값: 웹사이트 + Android + iPhone. 현장/키오스크 사용 시 태블릿 포함.
- 사용자 스토리 (목록, 백로그 내보내기, 기능 문서 등 어떤 형태든 가능).
- AC 형식: Given/When/Then 또는 일반 규칙? 기본값은 Given/When/Then.
- 우선순위(MoSCoW)와 상태(있는 경우). 없으면 기본값은
should/draft. - 주요 데이터 모델 (주요 레코드 + 핵심 필드) 또는 스토리에서 유추.
모든 스토리를 스토리 형식으로 정규화합니다. 사용자에게 스토리가 없으면 제품 설명에서 3~6개를 초안 작성한 후 빌드 전에 확인합니다.
-
스토리 확인 품질 체크리스트에 따라 검증합니다. 문제점을 짧은 목록으로 제시합니다. 사용자의 표현을 조용히 재작성하지 마십시오.
-
스토리별 화면 계획. 각 스토리에 대해 해당 스토리가 각 기기에서 사용하는 화면과 필요한 초기 데이터를 나열합니다. 스토리는 제품이 실제로 그렇게 작동하는 경우에만 특정 기기를 건너뜁니다(예: 웹사이트에서만 관리자 기능).
devices목록에 이를 명시하고, 해당 기기가 선택된 동안 전환기에서 다른 기기를 비활성화합니다. -
assets/shell.html복사 사용자가 선택한 경로(기본값docs/reference/prototype.html)로 복사하고 내용을 채웁니다.references/prototype-spec.md를 참조하여 시각적 규칙(프레임, 토큰, 유형, 카드, 알약, 토스트, 서랍, 아이콘, 복사 어조)을 확인합니다. -
스토리가 아닌 화면 구축. 화면은 공유되고, 스토리는 진입점과 초기 데이터 역할을 합니다. 스토리별로 화면을 절대 중복하지 마십시오. 스토리에 변형이 필요한 경우 초기 데이터를 통해 구동합니다.
-
브라우저에서 확인. 브라우저 도구가 있으면 사용합니다. 그렇지 않으면 headless Chrome 사용:
--dump-dom "file:///…/prototype.html#story=S2&device=iphone"으로 렌더링 결과 확인,--screenshot으로 시각 확인,--enable-logging=stderr로 콘솔 오류 확인. 확인 사항:- 목록에 있는 모든 기기의 모든 스토리가 올바른 화면에서 열리는지;
- 모든 AC를 시연할 수 있고, 연습 과정을 클릭하여 진행할 수 있는지;
- 스토리 도중 기기를 전환해도 상태가 유지되는지;
- 다른 스토리의 컨트롤이 현재 스토리 아래에 나타나지 않고, "모든 스토리"에서 모든 스토리의 컨트롤이 함께 작동하는지;
- 콘솔에 오류가 없는지.
-
동기화 유지. 나중에 스토리가 추가되거나 변경되면 동일한 편집에서
#story-text항목,BEHAVIOUR항목 및 해당 스토리가 나열된 모든 기기를 업데이트합니다.
스토리 형식
스토리는 셸에서 두 곳에 존재하며, 모두 ID로 키가 지정됩니다.
- 텍스트는
#story-textJSON 블록에 있으며, 스토리 패널에서 읽기 전용으로 표시됩니다. - 동작은
BEHAVIOUR에 있으며, 코드만 포함됩니다.
<script type="application/json" id="story-text">
{
"S1": {
"title": "검색 저장",
"story": "방문자로서 검색을 저장할 수 있어, 다시 검색하지 않고도 새로운 일치 항목을 알 수 있습니다.",
"priority": "must",
"status": "draft",
"ac": [
{ "id": "S1-AC1", "text": "Given \"오크 테이블\"을 검색했을 때, When 저장 검색을 탭하면, Then 저장됨 아래에 나타난다" },
{ "id": "S1-AC2", "text": "Given 해당 검색이 이미 저장되었을 때, When 다시 저장하면, Then 저장됨에 여전히 한 번만 표시된다" }
],
"acNext": 2,
"walkthrough": ["\"오크 테이블\" 검색", "저장 검색 탭", "저장됨 열기"],
"outOfScope": ["이메일 알림"],
"assumptions": ["저장하려면 방문자가 로그인해야 함"],
"questions": ["저장된 검색에 제한이 있나요?"]
}
}
</script>
const BEHAVIOUR = {
S1: {
devices: ['web', 'android', 'iphone'], // 이 스토리가 적용되는 프레임
start: { web: 'search', android: 'search', iphone: 'search' }, // 기기별 시작 화면
features: ['save-search'], // 이 스토리가 활성화하는 기능
seed: () => ({ user: SIGNED_IN, saved: [] }), // 이 스토리의 초기 데이터
},
};
| 필드 | 규칙 |
|---|---|
story | As a <persona>, I can <action>, so that <benefit>. 혜택은 사용자가 얻는 것이지, 행동의 재진술이 아니어야 합니다. |
priority | must · should · could · won't (MoSCoW). |
status | draft · in review · agreed. |
ac | 객체 { id, text }. ID는 <story>-AC<n> 형식이며, 절대 번호를 다시 매기거나 재사용하지 않습니다. 아래 참조. |
acNext | 이 스토리에 대해 발급된 가장 높은 AC 번호입니다. 새 AC는 acNext + 1을 사용하며, acNext도 업데이트됩니다. AC를 추가할 때마다 업데이트합니다. |
walkthrough | 검토자가 클릭하여 진행하는 데모 스크립트입니다. 수락 기준이 아닙니다. 짧게 유지하세요. |
outOfScope, assumptions, questions | 일반 목록입니다. 채우기 항목을 만들지 말고 비워 두세요. 사용자가 확실하지 않은 내용은 질문으로 기록하세요. |
seed()는 호출할 때마다 새 객체를 반환하므로, 스토리를 다시 선택하면 항상 깨끗하게 다시 시작됩니다. "모든 스토리"는 DEFAULT_SEED와 각 기기의 홈 화면을 사용합니다.
수락 기준
- AC당 하나의 테스트 가능한 동작, 사용자가 선택한 형식으로. Given/When/Then은 한 줄:
Given <컨텍스트>, when <액션>, then <관찰 가능한 결과>. - 정확한 문구: AC가 메시지에 관한 것일 때는 메시지를 인용하세요(
then I see "현재 열려 있는 항목이 없습니다."). "친근한 메시지"처럼 애매하게 쓰지 마십시오. - 결과, 스타일링이 아님: "완료로 표시됨"은 괜찮지만 "초록색 알약이 나타남"은 안 됩니다. 시각적 세부 사항은 그것이 요구 사항일 때만 AC가 됩니다.
- 한 기기에서 테스트 가능. 다른 기기에 대한 AC를 작성하지 마십시오(예: "모든 기기에 표시됨", "웹 전용", "휴대폰에서 변경 불가"). 스토리가 다루는 기기는
devices에 속하며, 기기 간 동기화는 백엔드의 책임입니다. - ID는 영구적입니다.
S1-AC3는 편집, 이동 또는 하위 스토리로 분할될 때도 ID를 유지하므로 테스트 및 티켓으로 추적할 수 있습니다. 새 AC는acNext + 1을 사용합니다. 삭제된 ID는 다시 발급되지 않으며, 번호를 다시 매기지 않습니다.
품질 체크리스트
빌드하기 전과 변경될 때마다 각 스토리를 확인하세요.
- 페르소나는 실제 사용자 역할이며, 혜택은 사용자가 실제로 말할 법한 내용인가?
- 독립적으로 가치를 제공하며(독립적), 1분 이내에 데모할 수 있을 정도로 작은가? 그렇지 않다면 분할하세요.
- 모든 AC에 ID, 단일 동작 및 관찰 가능한 결과가 있는가?
- 빈 상태, 오류 상태, 제한 사례가 다루어졌거나
questions아래에 나열되었는가? - 다른 스토리에 대한 의존성이 표시되었는가? (공유 기능 키와
assumptions에 언급, 예: "S2에 기반하여 구축").
스토리 기능은 해당 스토리가 선택될 때까지 비활성화됨
스토리가 도입하는 모든 상호작용은 해당 스토리의 features에 나열된 기능 키를 받습니다. can('save-search')는 해당 스토리를 나열하는 스토리가 선택된 경우에만 true입니다.
- 렌더링: 스토리의 컨트롤을
can(...)으로 감싸서 그렇지 않으면 존재하지 않도록 합니다(버튼 없음, 폼 없음, 메뉴 항목 없음). 비활성화된 상태로 표시하지 마십시오. 검토자는 선택한 스토리가 추가하는 내용만 볼 수 있어야 합니다. - 핸들러: 각 스토리 핸들러를
if (!can('save-search')) return;으로 시작하여, 오래된 DOM이나 콘솔에서도 스토리 외부에서는 아무것도 실행되지 않도록 합니다. - **"모든 스토리"**는 모든 스토리의 기능을 동시에 활성화합니다:
DEFAULT_SEED에서 현재 상태의 전체 제품. 스토리가 충돌하지 않는지 확인하십시오(예: 두 컨트롤이 같은 위치에 있는 경우). - 다른 스토리의 상호작용에 의존하는 스토리는 두 키를 모두 나열합니다(예: "상태별 필터링"은
['filter', 'complete']를 나열하여 작업 완료 시 필터링된 목록을 벗어나는 것을 보여줄 수 있음). - 스토리가 표시만 하는 데이터(예: 담당자의 이니셜)는 게이트되지 않습니다. 변경할 수 있는 기능만 게이트됩니다.
스토리 변경
프로토타입은 스토리를 표시만 할 뿐, 편집하거나 저장하지 않습니다. 모든 스토리 변경은 채팅을 통해 Claude를 통해 이루어집니다.
- 새 스토리: 스토리, AC 및 질문을 초안 작성하고, 사용자와 품질 체크리스트를 실행한 후,
#story-text항목과BEHAVIOUR항목을 작성하고 화면을 한 번에 구축합니다. 스토리는 텍스트로만 존재하지 않습니다. 그렇게 되면 셸이 전환기에서 제외하고 콘솔에 오류를 기록합니다. - 변경된 스토리(문구, AC, 우선순위, 상태, 메모):
#story-text를 편집합니다. 변경이 스토리의 동작을 변경하는 경우(새 AC, 다른 연습 과정), 동일한 편집에서 화면과BEHAVIOUR도 업데이트합니다. - 리뷰 피드백: 사용자가 검토자의 의견을 전달하면, 각각을 위의 변경 유형 중 하나로 변환하거나, 아직 해결되지 않은 경우
questions아래에 항목으로 추가합니다. - 항상 어떤 스토리와 AC ID를 빌드하거나 변경했는지 말하세요.
사용자가 Jira 등 트래커에 스토리를 보관하는 경우, 트래커가 마스터 사본입니다. 여기서는 스토리를 프로토타입에서만 편집하지 말고 트래커의 문구를 반영하세요.
스토리 분할
사용자가 스토리를 분할하도록 요청하거나, 스토리에 약 5개 이상의 AC, 둘 이상의 주요 기능 키 또는 별도로 출시할 수 있는 AC가 있는 경우 분할합니다.
-
분할 패턴 선택 및 사용한 패턴을 알립니다:
- 워크플로우 단계 (생성 → 검토 → 게시)
- 작업 (추가 / 편집 / 삭제)
- 먼저 해피 패스, 그 다음 에지 케이스 (빈 상태, 중복, 제한 도달)
- 기기 또는 역할별 (휴대폰 캡처 vs 웹사이트 관리)
- 데이터 변형별 (항목 하나 → 여러 개, 통화 하나 → 여러 개)
- 단순한 것 먼저, 그 다음 추가 기능 (일반 정렬 → 저장된 정렬 환경설정)
각 하위 스토리는 여전히 고유한 가치가 있는 완전한
As a… I can… so that…형식이어야 합니다. -
하위 스토리 번호를 부모에서 파생:
S3→S3.1,S3.2, … 부모를#story-text와BEHAVIOUR모두에서 제거하고 하위 스토리를 그 자리에 넣습니다. 셸의readHash는 이전#story=S3링크를S3.1로 보내므로 공유 링크가 계속 작동합니다. -
분할, 중복 금지:
- 수락 기준. 각 부모 AC는 원래 ID를 유지한 채 정확히 하나의 하위 스토리로 이동합니다 (
S3-AC2는S3.1내에서S3-AC2로 유지). AC가 두 하위 스토리에 걸쳐 있으면 두 개의 새 AC (S3.1-AC1,S3.2-AC1)로 다시 작성하고 대체하는 부모 ID를 기록합니다. - 기능 키. 상호작용이 분할되면 키도 분할합니다 (
filter→filter-status,filter-counts). 하위 스토리는 위의 의존성 규칙처럼 형제 스토리를 기반으로 구축할 때 그 키를 나열할 수도 있습니다. - 기기. 각 하위 스토리는 실제로 사용하는 기기만 나열합니다.
- 초기 데이터. 이후 하위 스토리의
seed()는 이전 하위 스토리가 남긴 상태에서 시작합니다(예: S3.2는 이미 저장된 일부 항목을 시드). 각 하위 스토리를 독립적으로 검토할 수 있습니다. - 메모. 각 하위 스토리에 적용되는 범위 외 항목, 가정 및 질문을 제공합니다. 사용자가 달리 말하지 않는 한 하위 스토리는 부모의 우선순위를 상속합니다.
- 수락 기준. 각 부모 AC는 원래 ID를 유지한 채 정확히 하나의 하위 스토리로 이동합니다 (
-
화면 업데이트: 다른 키로 이동한 컨트롤을 다시 게이트합니다. 그런 다음 각 하위 스토리가 나열된 모든 기기에서 확인하고 부모 ID가 리디렉션되는지 확인합니다.
-
분할 보고를 짧은 표(하위 스토리 ID, 스토리, 우선순위, 기기, AC ID)로 작성하여 사용자가 모든 부모 AC가 어딘가에 배치되었는지 확인할 수 있도록 합니다. 사용자가 스토리를 보관하는 경우 백로그나 기능 문서에도 분할을 반영합니다.
규칙
- 하나의 HTML 파일, 인라인 CSS/JS, 빌드 단계 없음, 프레임워크 없음, 백엔드 없음. 디스크에서 직접 엽니다.
- 모든 데이터는 하나의 공유 인메모리
state에 있습니다. 모든 기기가 읽고 씁니다. - 렌더 함수는 기기 ID를 받아
state에서 다시 그립니다(셸 참조). - 가상의 중립적인 샘플 데이터를 사용합니다. 다른 프로젝트의 이름, 브랜드 또는 콘텐츠를 프로토타입에 복사하지 마십시오.
- 모바일 프레임은 동일한 앱 마크업을 렌더링합니다. Android와 iPhone의 차이는 크롬(상태 표시줄, 노치/아일랜드, 제스처 바)일 뿐, 별도의 앱이 아닙니다. 사용자가 플랫폼별 UI를 요청하지 않는 한.