Files
Learn_System/plans/admin-redesign/phase-1-hash-router.md
T
Maxim Dolgolyov 76e376ee04 chore(plan): admin-redesign 6-phase plan
PLAN.md + 6 subplans + CONTEXT.md

Strategy: Incremental | Mode: Automated | Execution: Orchestrator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:47:55 +03:00

5.7 KiB
Raw Blame History

Phase 1: Hash-router

Status: Not Started Parent plan: PLAN.md Domain: frontend

Objective

Заложить фундамент для URL-роутинга admin-панели через location.hash. После этой фазы можно делать F5 на #users, делиться deep-links, использовать browser back/forward. Старая система табов (switchTab) продолжает работать без изменений — router её обёртывает, а не заменяет.

Tasks

  • Создать frontend/js/admin/router.js с window.AdminRouter:
    • parse(hash){ route: 'users', params: ['123'], raw }
    • navigate(routeOrHash, { replace?, silent? }) — программная навигация
    • current() → текущий route object
    • on(event, fn) / off(event, fn) — pub/sub для 'change' event
    • Поддержка форматов: #stats, #users, #users/123, #sessions/456
  • Подключить router.js в admin.html ДО admin.js
  • В admin.js модифицировать switchTab(btn):
    • Дополнительно вызывать AdminRouter.navigate('#' + name, { silent: true })
    • НЕ удалять старую логику
  • Добавить листенер AdminRouter.on('change', ...) в admin.js:
    • При route change → найти соответствующий .admin-nav-item[data-tab="X"] и активировать его (через имеющийся switchTab, но с silent-флагом чтобы избежать рекурсии)
  • При инициализации страницы:
    • Если location.hash пустой → set default #stats
    • Если есть hash → распарсить и переключить на соответствующий tab
  • Логировать unknown routes: console.warn('AdminRouter: unknown route', route) + fallback на #stats
  • Защита от инфинит-loop'а: флаг _routerNavigating при programmatic-навигации, чтобы handler не реагировал на свой же hash change

Files to Modify/Create

  • frontend/js/admin/router.js — новый, ~80-120L
  • frontend/admin.html — добавить <script src="/js/admin/router.js"></script> в <head> или перед admin.js
  • frontend/js/admin/admin.js — модифицировать switchTab + добавить init-логику (~15-25L изменений)

Acceptance Criteria

  • F5 на http://localhost:3000/admin#users восстанавливает users-tab
  • Browser back/forward переключают между табами (без полного reload)
  • Клик по admin-nav-item обновляет URL (#users появляется в адресной строке)
  • Клик по cross-tab handler типа goAddQuestion('bio') — старая логика работает, URL обновляется
  • Unknown hash (например #nonexistent) → console.warn + fallback на #stats, нет crash
  • #users/123 парсится корректно (params=['123']), но пока никто его не использует — Phase 6 подключит

Notes

Почему hash-router, а не history.pushState

Backend Express раздаёт admin.html по /admin. С pushState пришлось бы либо настраивать catch-all route на server-стороне (/admin/*), либо делать SPA-style роутинг. Hash-router работает out-of-the-box и не требует backend-изменений. Это критично для incremental-стратегии — мы не трогаем server в Phase 1.

Защита от рекурсии

Сценарий: пользователь кликает на tab → switchTab вызывает navigate → navigate меняет hash → срабатывает hashchange → router emits 'change' → handler вызывает switchTab → snake eats tail.

Решение:

let _navigating = false;
function navigate(hash) {
  _navigating = true;
  location.hash = hash;
  _navigating = false;
}
window.addEventListener('hashchange', () => {
  if (_navigating) return;
  // emit 'change'
});

Или передавать { silent: true } через объект-параметр и проверять его в handler'е switchTab.

Существующий пример hashchange

В frontend/js/textbook-tracker.js:438 уже есть addEventListener('hashchange', handleHashNav) — это safe-pattern, можно подсмотреть структуру.

Review Checklist

  • router.js не использует Grep / эмоджи / отсутствующие LS-помощники
  • Старый switchTab НЕ удалён, только обёрнут
  • Нет регрессий: все 13 табов переключаются, lazy-load работает
  • F5 / back / forward проверены вручную в браузере (или симуляция через subagent)
  • Default route #stats срабатывает при пустом hash
  • Unknown route не крашит панель
  • Код следует конвенциям проекта (no emoji, inline SVG для иконок, LS.* для API)
  • Build passes: cd backend && npm starthttp://localhost:3000/admin загружается

Handoff to Next Phase