## Design Файл: `/opt/vesti/web/app.py`, функция `approve`. Текущий порядок (баг): ```python if not dirs_selected: cls = conn.execute( "SELECT direction FROM classifications WHERE post_id=? ORDER BY id", (post_id,) ).fetchall() dirs_selected = [r["direction"] for r in cls] if cls else [dirn] # ← dirn не определён dirs_selected = list(dict.fromkeys([d for d in dirs_selected if d])) card = make_card(post, comment) dirn = post.get("direction") or "linux" # ← определяется ПОСЛЕ использования lang = post.get("lang") or "ru" ``` Правка (минимальная, чистая): перенести определение `dirn` и `lang` ДО строки `dirs_selected = ...`, сразу после `post = dict(post)` / вычисления `is_own`: ```python post = dict(post) is_own = int(post.get("is_own") or 0) == 1 dirn = post.get("direction") or "linux" # ← теперь определён lang = post.get("lang") or "ru" # ... (фан-аут направления из формы) dirs_selected = [r["direction"] for r in cls] if cls else [dirn] # ок dirs_selected = list(dict.fromkeys([d for d in dirs_selected if d])) card = make_card(post, comment) # dirn/lang уже определены выше, убрать поздние присваивания (строки 185-186) ``` Удалить поздние `dirn = ...` и `lang = ...` (строки 185-186), т.к. они станут дублями. ## Верификация - `openspec validate fix-approve-dirn` — чисто. - Перезапуск веба: `sudo systemctl restart vesti-web`. - Approve поста без выбранных направлений (пустая форма) → 302 на /candidates, пост публикуется (HTTP 200/302, в логах нет UnboundLocalError). - Approve поста с выбранными направлениями — тоже ок (регрессия).