Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions internal/web/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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;`) ||
Expand Down Expand Up @@ -1888,8 +1888,15 @@ func TestHandlerIndexServesHTML(t *testing.T) {
if !strings.Contains(markup, `<span class="sr-only">GitHub</span>`) {
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, `<span class="prompt-mode-link-tooltip" aria-hidden="true">Current Work</span>`) {
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")
Expand Down
10 changes: 10 additions & 0 deletions internal/web/static/bottom-dock.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
<span class="sr-only">JSON</span>
</a>
<span class="prompt-mode-divider" aria-hidden="true"></span>
<a class="prompt-mode-link site-dock-link" href="#current-work" data-app-display="work" aria-label="Current Work" title="Current Work">
<span class="prompt-mode-link-shell" aria-hidden="true"></span>
<span class="prompt-mode-link-icon" aria-hidden="true">
<i data-lucide="combine" aria-hidden="true"></i>
</span>
<span class="prompt-mode-link-line prompt-mode-link-line-top" aria-hidden="true"></span>
<span class="prompt-mode-link-line prompt-mode-link-line-bottom" aria-hidden="true"></span>
<span class="prompt-mode-link-tooltip" aria-hidden="true">Current Work</span>
<span class="sr-only">Current Work</span>
</a>
<a class="prompt-mode-link site-dock-link" href="#dashboard" data-app-display="dashboard" aria-label="Dashboard" title="Dashboard">
<span class="prompt-mode-link-shell" aria-hidden="true"></span>
<span class="prompt-mode-link-icon" aria-hidden="true">
Expand Down
13 changes: 9 additions & 4 deletions internal/web/static/bottom-dock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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}`}`);
});
});
}
Expand Down
24 changes: 15 additions & 9 deletions internal/web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ <h2 id="hub-setup-title">Connect to Hub</h2>
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,
Expand Down Expand Up @@ -7988,9 +7988,12 @@ <h2 id="hub-setup-title">Connect to Hub</h2>

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";
}

Expand Down Expand Up @@ -8048,17 +8051,17 @@ <h2 id="hub-setup-title">Connect to Hub</h2>
}

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";
}
Expand All @@ -8073,6 +8076,7 @@ <h2 id="hub-setup-title">Connect to Hub</h2>
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;
Expand Down Expand Up @@ -8113,7 +8117,9 @@ <h2 id="hub-setup-title">Connect to Hub</h2>
? "#dashboard"
: mode === "chat"
? "#chat"
: promptHashFromMode(state.promptMode);
: showWork
? "#current-work"
: promptHashFromMode(state.promptMode);
if (window.location.hash !== nextHash) {
window.history.pushState(null, "", nextHash);
}
Expand Down Expand Up @@ -11084,7 +11090,7 @@ <h2 id="hub-setup-title">Connect to Hub</h2>
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;
Expand Down
16 changes: 16 additions & 0 deletions internal/web/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down