Введение
Ключевое слово, категория, порядок и страница Fieldnote уже передаются в URL. Подготовленный контроллер после каждой отправки заменяет текущую запись истории. Прямые ссылки полезны, но предыдущая картина теряется: после нескольких поисков нельзя надёжно вернуться через Back. Вы отделите новые действия от восстановления и задействуете собственную историю браузера.
Эта независимая лабораторная готовит завершённые файлы выбора, нормализации и URL из предыдущей; старая VM не требуется. Нужны её правила четырёх состояний и основы функций, объектов, выбора DOM и событий JavaScript. Вы изучите 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, страница 2. Выберите Previous, измените Keyword на sound + light, Category на Listen, Sort by на Title A–Z и активируйте Search. Страница станет 1. Используйте Back браузера, не Previous сайта: восстановятся прежние слово, категория, порядок, страница и полные совпадающие события. Forward вернёт новое представление. Это обычный пример, а не завершение вашей работы.
Ниже независимое Study до редактирования. Его общий href описывает ту же видимую страницу, но не завершает Work.

Закройте Study и выберите Open Work view in a new page во входной странице Fieldnote. Все функции G1 присутствуют, но контроллер по-прежнему заменяет записи. Перейдите в Workspace. cd задаёт каталог постоянного терминала; cat читает файл, не меняя его:
cd /home/labex/project/fieldnote/work
cat app.js
Текущая show вызывает normalize, создаёт URL с stateURL, заменяет адрес и просит draw показать выбранные записи. Эти помощники приходят из подготовленных state.js, url.js, view.js, selection.js. Сохраните этот порядок, различая две причины показа представления.
history.pushState(null, '', url) добавляет запись того же origin и меняет адрес без запроса новой страницы. replaceState обновляет только текущую запись. Первый аргумент — необязательное связанное состояние; используем null, потому что всё необходимое уже в URL. Пустая строка — неиспользуемый аргумент заголовка API. Ни один вызов сам не вызывает popstate. Следующий шаг обработает реальный переход по истории. Начальная нормализация использует replace, не добавляя лишнюю запись сверх обычного открытия ссылки. Поиск и кнопки страниц используют push, когда подтверждённый URL меняется; неизменённому представлению не нужен дубликат.
document.querySelector('form') находит подготовленную форму. form.elements.q обращается к полю q. preventDefault останавливает стандартный переход формы, позволяя контроллеру обновить представление. Обработчик запускается при указанном событии. Spread-синтаксис {...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 на странице 2. Измените на sound + light, Listen, Title A–Z и отправьте: страница 1 покажет два разных звуковых события. Активируйте Open this view in a new page, обновите новую страницу. Должны сохраниться Title A–Z, правильные даты и места. Кэшированное предпочтение не нужно: все четыре значения принадлежат URL, в том числе нестандартный порядок. Закройте новую страницу, оставьте исходную.
Пока не ждите перерисовки Work от Back: новые записи есть, но обработчик восстановления отсутствует. Запустите Check этого шага. Он проверяет прямые URL, полезные новые записи и начальную замену, не требуя обработчик следующего шага. Если опечатка JavaScript остановила обновления, прочитайте cat app.js, сравните полный блок, замените и обновите. Сохранение исправления не очищает каталог и не требует сброса VM.
Восстанавливать представления настоящими Back и Forward
На этом шаге вы прочитаете URL после реального перехода истории и докажете восстановление той же картины в другом контексте браузера.
Back и Forward меняют текущую запись. Браузер создаёт popstate при переходе между подходящими записями того же документа; собственный вызов pushState его не создаёт. Слушайте настоящее событие, читайте текущий URL, нормализуйте и отображайте. Не делайте push из обработчика: вместо восстановления существующей записи появится новая. URL работает и без event.state в памяти, как при прямом открытии. Не используйте localStorage как источник истины и не храните порядок только в объекте истории.
Перейдите в Workspace. Добавьте обработчик к ранее сохранённому файлу. В отличие от >, >> добавляет, не удаляя существующее. Выполните блок один раз. При случайном двойном добавлении замените app.js предыдущим полным блоком и добавьте обработчик ровно один раз.
cat >> app.js <<'JS'
window.addEventListener('popstate', () => show(readURL(), 'replace'));
JS
Стрелочный callback выполняется при нативном переходе по истории. Он использует тот же show, поэтому поля, порядок записей, номер страницы, отключённые границы и реальный общий href соответствуют новому URL. Замена может нормализовать эквивалентный ввод, но не должна добавлять запись. Учебный путь не имитирует события и не использует невидимые тестовые обходы.
Вернитесь к исходной отдельной рабочей странице. Для ясной начальной записи откройте в адресной строке реальный /work/ URL с этим запросом, сохранив настоящие origin и путь:
?edition=november&q=print%20%26%20fold&category=make&sort=title&page=2#directory-title
Это состояние A: Title A–Z, страница 2, Print & Fold: map edition, затем Print & Fold: tiny type. Прочитайте время и места. Создайте B кнопкой Previous: на странице 1 две обычные Print & Fold, затем Print & Fold: colour. Создайте C отправкой sound + light, Listen, Soonest first: страница 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 и обновите. Должно остаться B, так как обновление читает URL. Изменённое, но не отправленное слово — черновик, который обновление отбрасывает. Явно проверьте: введите unsubmitted draft без Search, обновите и увидьте возврат подтверждённого слова B. Для обычной ошибки отправьте prnit, прочитайте корректное сообщение без результатов, исправьте на print & fold, Make и отправьте. Пустая выдача не должна порождать вымышленную запасную карточку.
На ширине рабочего стола, около 768 и 390 пикселей сравните полные карточки, поля и общую ссылку. Используйте Tab и Enter для Search, проверьте видимость фокуса и сохраните подготовленную адаптивную оболочку. Выполните Check. Он запускает неизменённый frontend изолированно, проверяет реальные Back/Forward и открытие в чистом контексте, сравнивает каждую видимую запись с независимым каталогом и сохраняет активное рабочее пространство. Он не создаёт аккаунт и не публикует постоянный сайт.
Резюме
Вы отделили новые подтверждённые действия от начального и исторического восстановления. Настоящие Back/Forward, прямые общие ссылки и обновление теперь используют одно состояние из URL и полные записи событий. Испытание перенесёт это на другой каталог и размер страницы без маршрутизатора, backend-пагинации или зависимости от частного хранилища.
