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

91 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 1: Hash-router
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./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.
Решение:
```js
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 start` → http://localhost:3000/admin загружается
## Handoff to Next Phase
<!-- Заполнит implementer после фазы.
Должно содержать: где живёт router API, какие события эмитятся, как Phase 2 sections подписываются на route changes. -->