diff --git a/internal/web/server_test.go b/internal/web/server_test.go
index bbca622a..c57b9d99 100644
--- a/internal/web/server_test.go
+++ b/internal/web/server_test.go
@@ -1292,9 +1292,9 @@ func TestHandlerIndexServesHTML(t *testing.T) {
!strings.Contains(markup, "return normalizeTaskStatusFilter(state.taskStatusFilter) === \"completed\" && runningCount === 0 && completedCount === 0;") {
t.Fatalf("expected index html to keep completed-history empty state visible only in completed-history view")
}
- if !strings.Contains(markup, `const showTaskPanel = state.appDisplay === "studio" || state.appDisplay === "chat";`) ||
+ if !strings.Contains(markup, `const showTaskPanel = state.appDisplay === "work";`) ||
!strings.Contains(markup, "taskPanel.classList.toggle(\"hidden\", !showTaskPanel);") {
- t.Fatalf("expected index html to show the task queue panel only on work-oriented app views")
+ t.Fatalf("expected index html to show the task queue panel only on the Current Work view")
}
if !strings.Contains(markup, "taskPanel.setAttribute(\"aria-hidden\", showTaskPanel ? \"false\" : \"true\");") {
t.Fatalf("expected index html to keep task panel aria visibility in sync with rendered content")
@@ -1683,19 +1683,19 @@ func TestHandlerIndexServesHTML(t *testing.T) {
!strings.Contains(markup, `activatePromptMode("json");`) {
t.Fatalf("expected bottom dock Library and JSON controls to activate their Studio views through the shared mode path")
}
- if !strings.Contains(markup, `let mode = display === "dashboard" || display === "chat" ? display : "studio";`) ||
+ if !strings.Contains(markup, `let mode = display === "dashboard" || display === "chat" || display === "work" ? display : "studio";`) ||
!strings.Contains(markup, `if (mode === "chat" && !state.githubReposReady) {`) ||
!strings.Contains(markup, `mode = "studio";`) ||
!strings.Contains(markup, `appLayout.hidden = false;`) ||
!strings.Contains(markup, `promptWrap.hidden = !showStudio;`) ||
- !strings.Contains(markup, `const showTaskPanel = state.appDisplay === "studio" || state.appDisplay === "chat";`) {
- t.Fatalf("expected index html to switch main views while hiding Current Work on dashboard")
+ !strings.Contains(markup, `const showTaskPanel = state.appDisplay === "work";`) {
+ t.Fatalf("expected index html to switch main views while isolating Current Work")
}
if !strings.Contains(markup, `function syncTaskVisibilityForAppDisplay()`) ||
- !strings.Contains(markup, "if (state.appDisplay === \"chat\") {\n state.taskVisible = false;\n return;\n }") ||
- !strings.Contains(markup, "if (state.appDisplay === \"studio\") {\n state.taskVisible = true;\n }") ||
+ !strings.Contains(markup, "if (state.appDisplay === \"work\") {\n state.taskVisible = true;\n return;\n }") ||
+ !strings.Contains(markup, "if (state.appDisplay === \"studio\" || state.appDisplay === \"chat\") {\n state.taskVisible = false;\n }") ||
!strings.Contains(markup, `syncTaskVisibilityForAppDisplay();`) {
- t.Fatalf("expected index html to minimize Current Work on chat entry while expanding it in Studio views")
+ t.Fatalf("expected index html to expand Current Work only on the Current Work view")
}
if !strings.Contains(markup, `function syncPromptTitleModes()`) ||
!strings.Contains(markup, `item.hidden = !active;`) ||
@@ -1888,8 +1888,15 @@ func TestHandlerIndexServesHTML(t *testing.T) {
if !strings.Contains(markup, `GitHub`) {
t.Fatalf("expected index html to keep the GitHub dock item screen-reader accessible without visible text")
}
- if strings.Index(markup, `id="task-panel"`) > strings.Index(markup, `class="panel prompt-wrap`) {
- t.Fatalf("expected index html to render Current Work before Studio in the page layout")
+ currentWorkDockIndex := strings.Index(markup, `data-app-display="work"`)
+ dashboardDockIndex := strings.Index(markup, `data-app-display="dashboard"`)
+ if currentWorkDockIndex < 0 || dashboardDockIndex < 0 || currentWorkDockIndex > dashboardDockIndex {
+ t.Fatalf("expected Current Work to render as a dock view before Dashboard")
+ }
+ if !strings.Contains(markup, `href="#current-work" data-app-display="work"`) ||
+ !strings.Contains(markup, `data-lucide="combine"`) ||
+ !strings.Contains(markup, `Current Work`) {
+ t.Fatalf("expected Current Work dock view to use the combine icon and label")
}
if !strings.Contains(markup, `id="builder-repo-select"`) {
t.Fatalf("expected index html to include repo history select")
diff --git a/internal/web/static/bottom-dock.html b/internal/web/static/bottom-dock.html
index 889dee41..d6236bb4 100644
--- a/internal/web/static/bottom-dock.html
+++ b/internal/web/static/bottom-dock.html
@@ -41,6 +41,16 @@
JSON
+
+
+
+
+
+
+
+ Current Work
+ Current Work
+
diff --git a/internal/web/static/bottom-dock.js b/internal/web/static/bottom-dock.js
index 7c87da3f..c0b1d8c4 100644
--- a/internal/web/static/bottom-dock.js
+++ b/internal/web/static/bottom-dock.js
@@ -177,11 +177,15 @@
function syncPageNavLinks(root) {
const hashDisplay = String(window.location.hash || "").replace(/^#/, "").trim();
- const currentDisplay = hashDisplay === "dashboard" || hashDisplay === "chat"
+ const hashAppDisplay = hashDisplay === "dashboard" || hashDisplay === "chat" || hashDisplay === "work"
? hashDisplay
- : normalizePath(window.location.pathname) === "/chat"
+ : hashDisplay === "current-work"
+ ? "work"
+ : "";
+ const currentDisplay = hashAppDisplay
+ || (normalizePath(window.location.pathname) === "/chat"
? "chat"
- : "studio";
+ : "studio");
const links = root.querySelectorAll("[data-app-display]");
links.forEach((link) => {
const linkDisplay = String(link.getAttribute("data-app-display") || "").trim() || "dashboard";
@@ -219,7 +223,8 @@
if (!display) {
return;
}
- window.location.assign(`${HOME_PATH}#${display}`);
+ const hash = String(link.hash || "").trim();
+ window.location.assign(`${HOME_PATH}${hash || `#${display}`}`);
});
});
}
diff --git a/internal/web/static/index.html b/internal/web/static/index.html
index c6a0c81c..36655dc1 100644
--- a/internal/web/static/index.html
+++ b/internal/web/static/index.html
@@ -914,7 +914,7 @@ Connect to Hub
return;
}
- const mode = appDisplay === "dashboard" || appDisplay === "chat" ? appDisplay : "studio";
+ const mode = appDisplay === "dashboard" || appDisplay === "chat" || appDisplay === "work" ? appDisplay : "studio";
const currentLocation = analyticsPageLocation();
const payload = {
send_to: GOOGLE_ANALYTICS_MEASUREMENT_ID,
@@ -7988,9 +7988,12 @@ Connect to Hub
function appDisplayFromHash() {
const hash = String(window.location.hash || "").replace(/^#/, "").trim().toLowerCase();
- if (hash === "dashboard" || hash === "chat") {
+ if (hash === "dashboard" || hash === "chat" || hash === "work") {
return hash;
}
+ if (hash === "current-work") {
+ return "work";
+ }
return "studio";
}
@@ -8048,17 +8051,17 @@ Connect to Hub
}
function syncTaskVisibilityForAppDisplay() {
- if (state.appDisplay === "chat") {
- state.taskVisible = false;
+ if (state.appDisplay === "work") {
+ state.taskVisible = true;
return;
}
- if (state.appDisplay === "studio") {
- state.taskVisible = true;
+ if (state.appDisplay === "studio" || state.appDisplay === "chat") {
+ state.taskVisible = false;
}
}
function setAppDisplay(display, options = {}) {
- let mode = display === "dashboard" || display === "chat" ? display : "studio";
+ let mode = display === "dashboard" || display === "chat" || display === "work" ? display : "studio";
if (mode === "dashboard" && !dashboardAvailable(state.snapshot)) {
mode = "studio";
}
@@ -8073,6 +8076,7 @@ Connect to Hub
const previousMode = state.appDisplay;
const showDashboard = mode === "dashboard";
const showChat = mode === "chat";
+ const showWork = mode === "work";
const showStudio = mode === "studio";
state.appDisplay = mode;
document.body.dataset.appDisplay = mode;
@@ -8113,7 +8117,9 @@ Connect to Hub
? "#dashboard"
: mode === "chat"
? "#chat"
- : promptHashFromMode(state.promptMode);
+ : showWork
+ ? "#current-work"
+ : promptHashFromMode(state.promptMode);
if (window.location.hash !== nextHash) {
window.history.pushState(null, "", nextHash);
}
@@ -11084,7 +11090,7 @@ Connect to Hub
const tasks = displayTasks(snapshot);
const hasTasks = tasks.length > 0;
const hasTaskPanelTasks = hasTaskPanelContent(snapshot);
- const showTaskPanel = state.appDisplay === "studio" || state.appDisplay === "chat";
+ const showTaskPanel = state.appDisplay === "work";
const promptOnly = normalizeTaskPanelView(state.taskPanelView) === "prompt";
if (!showTaskPanel && state.taskFullscreenOpen) {
state.taskFullscreenOpen = false;
diff --git a/internal/web/static/style.css b/internal/web/static/style.css
index 424bbb42..560d72e8 100644
--- a/internal/web/static/style.css
+++ b/internal/web/static/style.css
@@ -794,6 +794,22 @@ body[data-app-display="chat"] .chat-shell {
overflow: hidden;
}
+body[data-app-display="work"] .layout,
+body[data-app-display="work"] .right-col {
+ min-height: calc(100vh - 150px);
+ min-height: calc(100dvh - 150px);
+}
+
+body[data-app-display="work"] .right-col {
+ grid-template-rows: minmax(0, 1fr);
+}
+
+body[data-app-display="work"] #task-panel {
+ min-height: min(720px, calc(100vh - 150px));
+ min-height: min(720px, calc(100dvh - 150px));
+ height: 100%;
+}
+
.panel {
border-radius: var(--radius-card);
overflow: hidden;