fix(web): show only model in agent tab title (#2043)
This commit is contained in:
@@ -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 ({
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user