project_nav_current_project_context_prd.md 4.23 KB

name: project-nav-current-project-context-prd description: PRD "导航栏『当前项目』上下文(项目名+任务状态摘要)" reviewed 2026-08-06 for internal contradictions — none found metadata:

type: project

Reviewed the Planner-stage PRD titled "导航栏『当前项目』上下文(项目名 + 任务状态摘要)" (nav bar current-project display FR-1/2, clickable task-status-count summary FR-3/4, query-string parsing fix in frontend/app.js's render() FR-5, refresh-on-route-change FR-6; explicitly scoped out a new backend aggregation endpoint and nav-bar WebSocket live refresh) for internally contradictory requirements. Found none — the backend-endpoint scope-out is reasoned (existing no-pagination precedent + documented revisit threshold) and the route-change-only refresh (FR-6) doesn't conflict with skipping WebSocket live refresh (discrete-refresh vs push-refresh are different, compatible mechanisms).

Why: worth recording because a sibling PRD in the same pipeline (the v_graph_console Graph page PRD, see frontend/.graphlemon/runs/7400d876-a797-4d87-bcff-4e59a3befba1/prd.md) did have a real self-contradiction in its v1 draft (claimed both "shared render code with v_task_detail" and "v_task_detail's click behavior unaffected" with no reconciliation mechanism) that got caught by this same consistency-review step and fixed in v2 via an opt-in enableNodeClick parameter — so this check is not pro forma, it has caught a real issue before in this codebase.

How to apply: existing baseline in docs/sdd/02-V-界面与交互.md §nav only documents "当前项目名 + 切换项目链接" — no task-status-count summary yet. That's an SDD gap this PRD fills, not a contradiction. If this PRD proceeds to SDD stage, frontend/app.js's actual render() (line 118) only does hash-path regex routing (TASK_DETAIL_RE etc.) — no query-string (?key=value) parsing exists there at all today, so FR-5 ("fix") is really "add"; flag that wording precisely if it recurs at SDD/Coder stage (accuracy issue, not a contradiction, so it wasn't in-scope for this consistency review).

2026-08-07 re-confirmed while planning a Work Graph for the same goal (nav re-read fresh: frontend/index.html's <nav> is still 8 static <a>s, no widget). Reconfirmed facts, not yet superseded:

  • render() (app.js:118) is still the single right place to refresh the nav widget — both existing setCurrentProjectId() call sites (mountProjectList's "选择" button, app.js:230; mountProjectSetup's create-project success handler, app.js:353) already immediately set window.location.hash, which fires hashchange → render() on its own; no new event wiring needed for "switch project → nav updates".
  • Precedent for the "no new backend aggregation endpoint" call already exists in shipped code, not just reasoning: mountBudgetSettings (app.js ~2423-2429) already does fetch("/api/tasks?project_id=..&status=blocked_pending_human") and uses .length for a count. Generalizing this to all statuses = one unfiltered GET /api/tasks?project_id=... fetch + client-side reduce over TASK_STATUSES (app.js:844-847) — single round trip, not 9. task_query.list_tasks has no LIMIT/OFFSET anywhere (checked src/task_query.py), so "no pagination" is the whole codebase's existing convention, not a gap being introduced here.
  • New open decision surfaced this pass that the earlier PRD review didn't record: the user's example "done 12 · running 2 · pending 3" implies grouping graphlemon's 10 raw task statuses into ~3-4 display buckets, not a literal 10-number readout — PRD must pick and spell out the bucket→raw-status mapping explicitly (product decision), and must also decide the click target when a bucket maps to >1 raw status, since task_query.list_tasks's status param only accepts one value at a time.
  • render()'s nav a.active toggle (app.js:159-161) and its routes[hash]/regex-table lookup both do exact/full-hash matching — once a nav link produces #/tasks?status=done, render() must split path from query before route dispatch, or the lookup breaks. This is a required render() change, called out explicitly so SDD doesn't file it as a frontend-only add unrelated to routing.