소개
Fieldnote의 검색어, 카테고리, 정렬, 페이지는 이미 URL로 전달됩니다. 준비 컨트롤러는 검색마다 현재 방문 기록을 교체합니다. 직접 링크는 유용하지만 이전 화면을 잃기 때문에 여러 검색 후 Back으로 안정적으로 돌아갈 수 없습니다. 새 사용자 동작과 복원을 구분하여 브라우저 자체 기록을 사용하게 만듭니다.
이 독립 실습에는 이전 실습에서 완성한 선택·정규화·URL 파일이 준비되어 있으므로 이전 VM이 필요하지 않습니다. 이전 실습의 네 상태 규칙과 JavaScript 함수, 객체, DOM 선택, 이벤트 기초가 필요합니다. pushState, replaceState, popstate, 그리고 공유에 오래된 로컬 설정이나 메모리 기록 객체 대신 URL이 필요한 이유를 배웁니다. 비동기 요청이나 프레임워크 라우터는 없습니다. 40분은 작성자 예상 시간입니다.
Workspace와 Fieldnote를 사용합니다. /home/labex/project/fieldnote/work/app.js만 편집합니다. selection.js, state.js, url.js, catalog.js, view.js, HTML/CSS, 독립 Study와 서비스를 보존하세요. 렌더러는 준비되어 있으며 각 선택 행사의 실제 제목, 설명, 날짜, 시간, 장소를 표시합니다. 사이트는 영어로 유지됩니다. 링크는 이 VM의 공개 경로가 사용 가능할 때만 열리며 인터넷에 사이트를 배포하는 작업은 아닙니다.
확정한 사용자 변경을 방문 기록에 추가하기
이번 단계에서는 초기화를 기록 한 항목으로 유지하고 사용자가 다른 검색이나 페이지를 확정할 때 새 항목을 만듭니다.
Fieldnote에서 Open Study view in a new page를 선택합니다. 제품 내장 탭이 아닌 독립 브라우저 페이지입니다. print & fold, Make, Soonest first의 두 번째 페이지를 읽습니다. Previous를 고르고 Keyword를 sound + light, Category를 Listen, Sort by를 Title A–Z로 바꾸어 Search를 실행합니다. 페이지는 1로 돌아갑니다. 사이트 Previous가 아니라 브라우저 Back을 사용하면 이전 검색어, 카테고리, 정렬, 페이지, 완전한 일치 레코드가 복원됩니다. 브라우저 Forward로 새 화면을 복원합니다. 정상 예제일 뿐 여러분의 작업을 완성하지 않습니다.
아래는 편집 전의 독립 Study 화면입니다. 공유 href는 같은 가시 페이지를 나타내며 Work를 완성하지 않습니다.

Study를 닫고 Fieldnote 시작 페이지에서 Open Work view in a new page를 고릅니다. 파일에는 G1 함수가 모두 있지만 컨트롤러는 아직 기록을 교체합니다. Workspace로 전환합니다. cd는 지속 터미널의 디렉터리를 설정하고 cat은 파일을 변경하지 않고 읽습니다.
cd /home/labex/project/fieldnote/work
cat app.js
현재 show는 normalize를 호출하고 stateURL로 URL을 만들어 주소를 교체한 뒤 draw에 선택 레코드 표시를 요청합니다. 준비된 state.js, url.js, view.js, selection.js의 보조 함수입니다. 이 순서를 유지하면서 화면을 보여 주는 두 가지 이유를 구분합니다.
history.pushState(null, '', url)은 같은 출처의 기록 항목을 추가하고 새 페이지를 요청하지 않고 주소를 바꿉니다. replaceState는 현재 항목만 갱신합니다. 첫 인수는 선택적 연결 상태이며 필요한 상태가 URL에 있으므로 null을 사용합니다. 빈 문자열은 API에서 사용하지 않는 제목 인수입니다. 어느 호출도 스스로 popstate를 발생시키지 않습니다. 실제 기록 탐색은 다음 단계에서 다룹니다. 초기 정규화는 replace를 사용하므로 링크를 여는 일반 탐색 외에 추가 기록을 만들지 않습니다. 검색과 페이지 버튼은 확정 URL이 달라질 때 push를 쓰며 같은 화면을 재제출할 때 중복은 필요하지 않습니다.
document.querySelector('form')은 준비된 폼을 찾습니다. form.elements.q는 이름이 q인 필드에 접근합니다. preventDefault는 기본 폼 이동을 막아 컨트롤러가 화면을 갱신하도록 합니다. 이벤트 리스너는 지정한 이벤트 때 실행됩니다. 전개 구문 {...current, page: ...}은 현재 상태 필드를 복사한 뒤 페이지를 덮어써 확정 필터를 보존합니다. 필드만 편집하면 Search 전에는 초안이며 페이지 버튼은 확정 상태를 씁니다.
다음 전체 내용으로 저장합니다. cat > app.js는 그 파일만 교체합니다. 따옴표가 있는 here-document <<'JS'는 내용을 그대로 보존하며 JS만 있는 줄로 끝냅니다.
cat > app.js <<'JS'
const form = document.querySelector('form');
let current;
function show(raw, mode) {
current = normalize(raw);
const url = stateURL(current);
if (mode === 'push' && url.href !== location.href) {
history.pushState(null, '', url);
} else if (mode === 'replace') {
history.replaceState(null, '', url);
}
draw(current, matches(current));
}
form.addEventListener('submit', event => {
event.preventDefault();
show({q: form.elements.q.value, category: form.elements.category.value,
sort: form.elements.sort.value, page: 1}, 'push');
});
document.querySelector('#previous').addEventListener('click', () =>
show({...current, page: current.page - 1}, 'push'));
document.querySelector('#next').addEventListener('click', () =>
show({...current, page: current.page + 1}, 'push'));
show(readURL(), 'replace');
document.documentElement.dataset.ready = 'true';
JS
&&는 두 조건을 모두 요구하므로 push 요청이고 주소가 다를 때만 방문 기록을 만듭니다. else if는 초기화에서 교체를 선택합니다. 준비 파일은 정규화와 코스의 네 쿼리 키만 관리하며 실제 프록시 경로, 추가 파라미터, 프래그먼트는 보존합니다. 끝의 data-ready는 컨트롤러가 초기 표시를 마쳤다는 표식입니다. HTML 필드가 나타났다고 JavaScript가 끝났다고 생각하지 마세요.
독립 작업 페이지로 돌아가 새로 고칩니다. print & fold, Make, Soonest first를 검색하고 Next를 선택합니다. 두 번째 페이지의 지도 버전과 North room 행사를 읽습니다. sound + light, Listen, Title A–Z로 바꾸어 보내면 첫 페이지에 서로 다른 소리 행사 두 개가 나옵니다. Open this view in a new page를 실행하고 새 페이지를 새로 고칩니다. Title A–Z와 올바른 날짜·장소가 유지되어야 합니다. 캐시 설정은 불필요합니다. 기본값 아닌 정렬까지 네 확정값이 모두 공유 URL에 있어야 합니다. 새 페이지를 닫고 원래 페이지를 유지합니다.
Back이 아직 작업 페이지를 다시 그릴 것이라고 기대하지 마세요. 새 기록 항목은 있지만 복원 리스너가 없습니다. 이 단계 Check를 실행하면 직접 URL, 유용한 새 항목, 초기 교체를 검사하며 다음 단계 리스너는 요구하지 않습니다. JavaScript 오타로 갱신이 멈추면 cat app.js로 전체 블록과 비교하여 교체하고 새로 고칩니다. 수정 저장은 카탈로그를 지우지 않으며 VM 재설정도 필요하지 않습니다.
실제 Back과 Forward로 화면 복원하기
이번 단계에서는 실제 기록 이동 후 URL을 읽고 다른 브라우저 컨텍스트에서도 같은 화면을 복원할 수 있음을 확인합니다.
Back과 Forward는 현재 기록 항목을 바꿉니다. 브라우저가 같은 문서의 해당 항목을 이동하면 popstate를 발생시키지만 pushState 호출 자체는 발생시키지 않습니다. 실제 이벤트를 듣고 현재 URL을 읽어 정규화하고 표시합니다. 리스너에서 push하지 마세요. 기존 항목을 복원하는 대신 새 항목을 만들게 됩니다. 직접 링크처럼 메모리에 event.state가 없는 경우도 URL은 동작해야 합니다. localStorage를 기준으로 삼거나 정렬을 기록 객체에만 저장하지 마세요.
Workspace로 전환하여 이전에 저장한 파일에 리스너 하나를 붙입니다. >와 달리 >>는 기존 내용을 지우지 않고 추가합니다. 한 번만 실행하세요. 실수로 두 번 붙였다면 이전 전체 블록으로 app.js를 교체하고 한 번만 추가합니다.
cat >> app.js <<'JS'
window.addEventListener('popstate', () => show(readURL(), 'replace'));
JS
화살표 콜백은 네이티브 기록 이동 때 실행됩니다. 같은 show 경로를 사용하므로 필드, 레코드 순서, 페이지 표시, 비활성 경계, 실제 공유 href가 새로운 현재 URL과 일치합니다. 교체를 통해 동등한 입력을 정규화해도 되지만 기록 항목을 더하지는 않습니다. 학습 흐름에서 이벤트를 인위적으로 발생시키거나 보이지 않는 테스트 지름길을 쓰지 않습니다.
원래 독립 작업 페이지로 돌아갑니다. 명확한 초기 항목을 위해 주소 표시줄에서 실제 /work/ URL을 다음 쿼리로 열고 실제 출처와 경로를 유지합니다.
?edition=november&q=print%20%26%20fold&category=make&sort=title&page=2#directory-title
상태 A입니다. Title A–Z, 두 번째 페이지, Print & Fold: map edition, 다음 Print & Fold: tiny type입니다. 시간과 장소를 읽습니다. Previous로 B를 만들면 첫 페이지에 일반 Print & Fold 두 개와 Print & Fold: colour가 있습니다. sound + light, Listen, Soonest first를 제출해 C를 만듭니다. 1페이지 중 1페이지이며 서로 다른 날짜 두 개입니다. edition과 프래그먼트가 유지되어야 합니다. 필터를 바꾸면 페이지는 처음으로 돌아갑니다.
브라우저의 실제 Back을 두 번 사용해 C → B → A로 돌아갑니다. 각 항목에서 모든 필드, 페이지, 정렬된 제목, 각 행사의 날짜·시간·장소를 비교합니다. 다음 Forward 두 번으로 A → B → C로 갑니다. Back/Back/Forward/Forward를 한 번 더 반복합니다. 복원 과정에서 몰래 기록을 추가하거나 앞으로 갈 기록을 잃으면 안 됩니다. 제품 탭 전환은 기록 이동이 아니며 사이트 Previous는 결과 페이지 조작이지 브라우저 Back이 아닙니다.
C에서 실제 Open this view in a new page 링크를 엽니다. 새 페이지는 같은 확정 상태여야 합니다. 새로 고친 뒤 다시 비교하세요. 검증기는 이전 localStorage나 기록 상태가 없는 깨끗한 컨텍스트에서도 정확한 생성 링크를 엽니다. 일반 브라우저의 새 페이지는 별도 탭이지 모든 저장소가 격리된다는 보장이 아닙니다. 클립보드 API나 검증하지 않은 Copied 메시지는 필요하지 않습니다.
원래 페이지로 돌아와 Back으로 B로 간 뒤 새로 고칩니다. URL을 읽기 때문에 B가 남아야 합니다. 변경만 하고 제출하지 않은 검색어는 새로 고칠 때 버리는 초안입니다. Search 없이 unsubmitted draft를 입력해 새로 고치고 B의 확정 검색어가 복원되는지 직접 확인합니다. 일반 오류로 prnit를 제출하여 올바른 빈 결과 메시지를 읽고 print & fold, Make로 고쳐 제출합니다. 빈 결과에 가짜 대체 카드를 만들면 안 됩니다.
데스크톱, 약 768픽셀, 약 390픽셀에서 완전한 카드, 컨트롤, 공유 링크를 비교합니다. Tab과 Enter로 검색하고 보이는 포커스를 확인하며 준비된 반응형 외형을 유지합니다. Check를 실행합니다. 검증은 변경하지 않은 프런트엔드를 격리 환경에서 실행하고 실제 Back/Forward와 깨끗한 컨텍스트를 시험하며 모든 가시 레코드를 독립 카탈로그와 비교하고 실제 작업 영역을 보존합니다. 계정을 만들거나 영구 사이트를 배포하지 않습니다.
요약
새 확정 동작과 초기·기록 복원을 구분했습니다. 실제 Back/Forward, 직접 공유 링크, 새로 고침이 같은 URL 기반 상태와 완전한 행사 레코드를 사용합니다. 챌린지에서는 라우터, 백엔드 페이지 처리, 개인 저장소 의존성을 더하지 않고 다른 카탈로그와 페이지 크기에 이 동작을 적용합니다.
