fix(web): show only model in agent tab title (#2043)

This commit is contained in:
Carlos Florêncio
2026-07-29 19:07:58 +01:00
committed by GitHub
parent e77788706e
commit 18bf81819e
7 changed files with 160 additions and 11 deletions
@@ -96,13 +96,23 @@ describe("resolveSessionTabTitle", () => {
});
describe("resolveSessionTabTitle fallbacks", () => {
it("includes non-model config selections in the title", () => {
it("excludes non-model config selections from the title", () => {
expect(
resolveSessionTabTitle({
...baseArgs,
agentLabel: null,
activeModelId: SPARK_MODEL_ID,
configOptions: [
{
type: "select",
id: "mode",
name: "Mode",
currentValue: "default",
options: [
{ value: "default", name: "Default" },
{ value: "plan", name: "Plan" },
],
},
{
type: "select",
id: "model",
@@ -125,7 +135,7 @@ describe("resolveSessionTabTitle fallbacks", () => {
},
],
}),
).toBe(`${SPARK_MODEL_NAME} / High`);
).toBe(SPARK_MODEL_NAME);
});
it("falls back to the agent label when live model state is unavailable", () => {
@@ -33,11 +33,7 @@ function resolveModelTitle(
// active/current model switches are reflected immediately in the tab title.
modelLabel = optionName(modelConfig, modelId);
}
const extras = args.configOptions
.filter((option) => !isModelConfigOption(option))
.map((option) => optionName(option, option.currentValue))
.filter(Boolean);
return [modelLabel, ...extras].join(" / ");
return modelLabel;
}
export function resolveSessionTabTitle(args: ResolveSessionTabTitleArgs): string | null {
@@ -79,6 +79,7 @@ test.describe("Mobile chat model selector", () => {
testPage,
apiClient,
seedData,
prCapture,
}, testInfo) => {
const { task } = await seedAndOpenTask(testPage, apiClient, seedData);
expect(task.session_id).toBeTruthy();
@@ -164,6 +165,9 @@ test.describe("Mobile chat model selector", () => {
body: await testPage.screenshot(),
contentType: "image/png",
});
await prCapture.screenshot("mobile-model-selector", {
caption: "Mobile model selector remains reachable by touch",
});
const viewport = testPage.viewportSize();
expect(viewport).not.toBeNull();
@@ -172,6 +172,7 @@ test.describe("Chat model selector — persistence", () => {
testPage,
apiClient,
seedData,
prCapture,
}) => {
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
@@ -197,7 +198,10 @@ test.describe("Chat model selector — persistence", () => {
await expect(trigger).toContainText("Mock Smart", { timeout: 5_000 });
const agentTab = session.sessionTabBySessionId(task.session_id);
await expect(agentTab).toContainText("Mock Smart", { timeout: 5_000 });
await expect(agentTab).toHaveText("Mock Smart", { timeout: 5_000 });
await prCapture.screenshot("desktop-agent-tab-model-only", {
caption: "Desktop agent tab shows only the selected model",
});
await expect
.poll(async () => {
const { sessions } = await apiClient.listTaskSessions(task.id);
@@ -219,7 +223,7 @@ test.describe("Chat model selector — persistence", () => {
await session.waitForLoad();
await session.waitForChatIdle({ timeout: 30_000 });
await expect(trigger).toContainText("Mock Smart", { timeout: 15_000 });
await expect(agentTab).toContainText("Mock Smart", { timeout: 15_000 });
await expect(agentTab).toHaveText("Mock Smart", { timeout: 15_000 });
});
test("completed turn metadata keeps changed options after a page reload", async ({
+61
View File
@@ -0,0 +1,61 @@
---
spec: docs/specs/ui/acp-model-configuration-summary.md
created: 2026-07-29
status: complete
---
# Implementation Plan: Model-only Agent Tab Title
## Overview
PR [#2021](https://github.com/kdlbs/kandev/pull/2021) made unnamed agent tabs follow the
authoritative live model across model switches and task-detail reloads. Its title resolver also
appends every non-model ACP config value, producing labels such as
`GPT-5.6-Terra / Default / High / Off`. Narrow the derived tab title to the model display name while
preserving the PR's live-state precedence, reload behavior, and custom-name override.
## Frontend
### Agent tab title resolver
- Update `apps/web/components/task/session-tab-title.ts` so `resolveModelTitle` resolves only the
provider-supplied display name for the chosen model.
- Continue using the live model config option to identify and display the authoritative current
model before the active model, agent-profile fallback, and start-time snapshot fallbacks.
- Keep user-supplied session names as the highest-precedence title.
- Do not change `ModelConfigSelector`, the task-chat compact changed-value summary, tooltips, or
agent-profile selector labels.
### Mobile design contract
`SessionTab` is mounted only by `DockviewDesktopLayout`; `TaskLayout` routes phone and
coarse-pointer tablet viewports to `SessionMobileLayout` and `SessionTabletLayout`. This copy-only
change therefore has no mobile tab surface, navigation, touch, scroll, or geometry impact. The
nearest mobile exemplar is the existing task mobile model selector, whose option access and compact
summary remain unchanged. No new mobile Playwright case is required; the focused desktop rendered
check proves the only affected surface.
## Tests
- **What:** A derived tab title contains the model display name and excludes non-model ACP config
values while preserving custom-name and fallback precedence.
**File:** `apps/web/components/task/session-tab-title.test.ts`.
**How:** Vitest table/fixture coverage around `resolveSessionTabTitle`, including live model,
mode, and effort options.
## E2E Tests
- **Scenario:** GIVEN an unnamed agent tab with provider mode and effort options, WHEN the user
switches to `Mock Smart`, THEN the tab text is exactly `Mock Smart`; WHEN the page reloads, THEN
the tab still contains only `Mock Smart`.
**File:** `apps/web/e2e/tests/chat/model-selector-error.spec.ts`.
**What to verify:** The stable session-ID tab locator has the exact model-only label before and
after reload, while the session model selector continues to show the selected model.
## Implementation
- [x] [Task 01: Render model-only agent tab titles](task-01-render-model-only-title.md)
## Open Questions
None.
@@ -0,0 +1,74 @@
---
id: "01-render-model-only-title"
title: "Render model-only agent tab titles"
status: done
wave: 1
depends_on: []
plan: "plan.md"
spec: "../../specs/ui/acp-model-configuration-summary.md"
---
# Task 01: Render Model-only Agent Tab Titles
## Acceptance
- An unnamed agent tab resolves to the authoritative current model display name without appending
session mode or non-model ACP config values.
- A live model switch and task-detail reload retain the model-only title.
- A user-supplied session name still overrides every derived title, and task-chat/profile model
selector summaries remain unchanged.
## Verification
```bash
cd apps && rtk pnpm --filter @kandev/web test -- --run components/task/session-tab-title.test.ts
cd apps/web && rtk pnpm e2e:run tests/chat/model-selector-error.spec.ts -- --grep "agent tab keeps the selected model title after reload"
```
## Files Likely Touched
- `apps/web/components/task/session-tab-title.ts`
- `apps/web/components/task/session-tab-title.test.ts`
- `apps/web/e2e/tests/chat/model-selector-error.spec.ts`
- `apps/web/e2e/tests/chat/mobile-model-selector.spec.ts` (PR evidence capture only)
- `docs/specs/ui/acp-model-configuration-summary.md`
- `docs/plans/agent-tab-model-only/plan.md`
- `docs/plans/agent-tab-model-only/task-01-render-model-only-title.md`
## Dependencies
None.
## Parallelism
`sequential`. The resolver, its unit contract, and the existing persistence E2E form one focused
behavioral change.
## Inputs
- Spec **What** and unnamed-agent-tab **Scenario**.
- PR #2021 and its `resolveSessionTabTitle` precedence and reload regression.
- Existing `SessionPage.sessionTabBySessionId` stable E2E locator.
- `TaskLayout` mobile/tablet branching, which confirms this Dockview tab has no mobile surface.
## TDD Sequence
1. Change the non-model-config unit case to require the exact model-only label and run the focused
Vitest command to confirm RED.
2. Remove non-model config composition from the derived model title without changing title
precedence or selector code, then rerun Vitest to GREEN.
3. Tighten the existing model-switch persistence E2E to require the exact tab label before and
after reload, then run the managed focused E2E against freshly built assets.
4. Mark this task `done`, mark the plan checkbox complete, and return the spec to `shipped`.
## Risks
- Model display names can come from either the model list or the live model config option; removing
the extra values must not bypass the provider-supplied model label.
- Dockview restoration can reapply stale panel titles; the existing session-tab component sync and
reload E2E must continue proving the rendered tab wins.
## Output Contract
Report the expected RED assertion, resolver change, exact unit and E2E command results, files
changed, mobile no-surface confirmation, remaining risks, and task/plan/spec status updates.
@@ -20,7 +20,7 @@ ACP agents can advertise an arbitrary ordered set of model-adjacent session conf
- Hovering or focusing the closed task selector shows a compact tooltip containing every currently rendered selector option as provider-supplied `Name: Value` rows, including baseline-matching values. The tooltip contains no descriptions or inferred provider knowledge. Opening the selector shows compact option names and selected values; entering an option submenu shows that option's provider description and the provider descriptions of its selectable values when supplied.
- Kandev preserves optional ACP descriptions for both top-level config options and selectable values throughout the adapter, backend event, WebSocket, store, and selector pipeline. Missing descriptions produce no invented or hard-coded explanatory text.
- Task-detail boot data includes the last persisted model list, live config options, and provider-default baseline so the compact label is complete on the first render instead of repainting after WebSocket reconnection.
- An unnamed agent tab derives its title from the task session's authoritative current model and live config values. The title updates when the model changes and remains correct after a task-detail refresh; a user-supplied session name continues to override the derived title.
- An unnamed agent tab derives its title from the task session's authoritative current model. Live config values may identify the current model, but non-model config values and the session mode are never appended to the tab title. The title updates when the model changes and remains correct after a task-detail refresh; a user-supplied session name continues to override the derived title.
- Each turn stores an immutable configuration snapshot when the turn is created. The snapshot contains the effective model, mode, ordered selected config values with their provider-supplied display names, and the task-session provider-default baseline used for comparison. Agent-message metadata renders from this turn snapshot instead of the session's latest mutable runtime configuration.
- Agent-message metadata always shows the attributed model and shows only non-model config values that differ from the turn's captured baseline. It keeps option names in the compact row because message attribution is read outside the selector context. Baseline-matching values and the session mode are omitted from the compact row; the mode remains available in turn metadata.
- Prompt-usage metadata may refine the turn's attributed model when the provider reports the actual model used, but it does not replace the turn's captured config values or baseline.
@@ -40,7 +40,7 @@ ACP agents can advertise an arbitrary ordered set of model-adjacent session conf
- **GIVEN** an ACP option or value supplies a description, **WHEN** the user enters that option's submenu, **THEN** Kandev shows the provider text. The closed trigger and top-level option list remain compact, and missing descriptions leave the description region absent.
- **GIVEN** a task selector has current model and config values, **WHEN** the user hovers or focuses its closed trigger, **THEN** a compact tooltip lists every selector option as `Name: Value` without provider descriptions, including values omitted from the changed-only trigger label.
- **GIVEN** a task session has persisted dynamic model configuration, **WHEN** the task-detail page is refreshed, **THEN** the first rendered selector label includes all changed values without waiting for a WebSocket event.
- **GIVEN** an unnamed agent tab whose selected model changes during the session, **WHEN** the model update converges or the task-detail page is refreshed, **THEN** the existing tab title shows the current selected model instead of the agent profile's original model label.
- **GIVEN** an unnamed agent tab whose selected model changes during the session, **WHEN** the model update converges or the task-detail page is refreshed, **THEN** the existing tab title shows only the current selected model instead of the agent profile's original model label or any session mode and non-model config values.
- **GIVEN** turn A runs with reasoning `High`, **WHEN** reasoning changes to `Low` before turn B, **THEN** turn A continues to show `Reasoning effort: High` and turn B shows `Reasoning effort: Low`.
- **GIVEN** a turn uses only provider-default options, **WHEN** its agent-message metadata renders, **THEN** the compact row shows only the attributed model.
- **GIVEN** a legacy turn has no configuration snapshot, **WHEN** the session's runtime options later change, **THEN** the legacy turn does not display those current options.