diff --git a/frontend/app.js b/frontend/app.js index c746273..eaf6426 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1049,7 +1049,11 @@ function mountTaskBoard(queryParams) { } rowsEl.innerHTML = tasks.map((t) => ( '' - + "" + t.task_id + "" + // task_id links straight to Graph 控制台 (#/graph?task_id=...) so a + // task can be located there directly, without going through the row's + // own click-through to #/tasks/:task_id first -- stopPropagation on + // the anchor keeps that row click-through intact for the other cells. + + '' + t.task_id + "" + "" + escapeHtml(t.goal_text) + "" + '' + t.status + "" + "" + t.assigned_org_node + "" @@ -2037,7 +2041,7 @@ function renderGraphConsole() { `; } -function mountGraphConsole() { +function mountGraphConsole(queryParams) { const projectId = getCurrentProjectId(); if (!projectId) return; @@ -2046,7 +2050,12 @@ function mountGraphConsole() { const workGraphPanelsEl = document.getElementById("gc_work_graph_panels"); const artifactsOverviewEl = document.getElementById("gc_artifacts_overview"); - let currentTaskId = null; + // "#/graph?task_id="-style deep link (e.g. from v_task_board's task_id + // column) preselects this task on first load, same pattern as + // pickInitialTaskBoardStatus. populateTaskSelect below already falls back + // to the default "first non-done" task if this id doesn't match any task + // in the project, so an unknown/stale id is silently ignored. + let currentTaskId = (queryParams && queryParams.task_id) || null; // Same "track open log viewers, close them all on re-render/route-leave" // pattern as mountTaskDetail's activeLogViewers/closeAllLogViewers, so a @@ -2083,6 +2092,13 @@ function mountGraphConsole() { async function loadTaskAndRender(taskId) { closeAllLogViewers(); currentTaskId = taskId; + // Keep the URL in sync with whichever task is actually showing -- + // including the initial auto-preselect, not just explicit dropdown + // switches -- so the address bar is always a valid deep link back to + // this exact task (same history.replaceState/no-hashchange pattern + // v_task_detail's org-strip click-through already uses above). + const newHash = "#/graph?task_id=" + encodeURIComponent(taskId); + if (window.location.hash !== newHash) history.replaceState(null, "", newHash); workGraphPanelsEl.innerHTML = '
加载中…
'; const resp = await fetch("/api/tasks/" + encodeURIComponent(taskId)); if (resp.status !== 200) {