## Design ### 1. `_fetch_candidates` — статус по умолчанию vs явный Текущее (строки ~151-166): ```python where.append("p.is_own=0") where.append("p.status='new'") ... if status and status != "new": where.append("p.status=?") params.append(status) ``` Правка: ```python where.append("p.is_own=0") if not status: # по умолчанию — только кандидаты (new); явный статус ниже where.append("p.status='new'") else: # явный статус (new/rejected/published) — единственный фильтр статуса where.append("p.status=?") params.append(status) if direction: where.append("p.direction=?") params.append(direction) if own in ("1", "0"): where.append("p.is_own=?") params.append(int(own)) if q: where.append("(p.text LIKE ? OR p.summary LIKE ?)") params += [f"%{q}%", f"%{q}%"] ``` ### 2. Скрытые поля в формах правой панели В `candidates.html` в каждую форму правой панели (approve, reclassify, rewrite, reject, comment) добавить: ```html ``` (Переменные `group_by`, `direction`, `own`, `q` уже доступны в контексте рендера роута `/candidates`.) ### 3. Роуты POST-действий — сохранять контекст Общий хелпер для построения URL возврата: ```python def _cand_back(group_by="", direction="", own="", q="", **extra): parts = [] if group_by: parts.append(f"group_by={group_by}") if direction: parts.append(f"direction={direction}") if own in ("0", "1"): parts.append(f"own={own}") if q: parts.append(f"q={quote(q)}") for k, v in extra.items(): if v: parts.append(f"{k}={v}") return "/candidates?" + "&".join(parts) if parts else "/candidates" ``` Применение: - `reject(post_id, request, group_by="", direction="", own="", q="")`: `return RedirectResponse(url=_cand_back(group_by, direction, own, q, status="rejected"), status_code=302)` → пользователь остаётся в своей группировке, видит список отклонённых. - `comment`/`reclassify`/`rewrite`: `_cand_back(group_by, direction, own, q, selected=post_id)`. - `approve`: после успеха — `_cand_back(group_by, direction, own, q, status="published")` (пользователь видит опубликованные в той же группировке) или `/published`. Решение: approve возвращает в кандидаты с `status=published` в той же группировке, чтобы контекст не терялся. Ошибки — `_cand_back(..., selected=post_id, error=err)`. FastAPI: параметры форм объявляются как `group_by: str = Form("")` и т.д. ## Верификация - `openspec validate keep-candidates-context` — чисто. - Юнит: `_fetch_candidates(c,'','rejected','','','date')` → возвращает отклонённые (не пусто); `_fetch_candidates(c,'','','','','date')` → new внешние. - Живой: POST /posts/{id}/reject с form group_by=date → редирект на `/candidates?group_by=date&status=rejected`, список не пуст. - Рестарт vesti-web.