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
30 changes: 20 additions & 10 deletions src/chat-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3552,31 +3552,35 @@ function SidebarComponent(props: any) {
<div className="sidebar-title">Notebook Intelligence</div>
{NBIAPI.config.isInClaudeCodeMode && (
<>
<div
<button
type="button"
className="user-input-footer-button"
onClick={() => startNewChatSession()}
title="Start a new chat session (restarts the Claude client)"
aria-label="Start a new chat session"
>
<VscAdd />
</div>
<div
</button>
<button
type="button"
className="user-input-footer-button"
onClick={() => setShowClaudeSessionPicker(true)}
title="Resume previous Claude session"
aria-label="Resume previous Claude session"
title="Resume a Claude session you started earlier in this workspace"
>
<VscHistory />
</div>
</button>
</>
)}
<div
<button
type="button"
className="user-input-footer-button"
onClick={() => handleSettingsButtonClick()}
title="Open Notebook Intelligence settings"
aria-label="Open Notebook Intelligence settings"
title="Configure providers, API keys, MCP servers, and skills"
>
<VscSettingsGear />
</div>
</button>
</div>
<div className="nbi-status-banner-live" aria-live="polite">
{skillsReloadedVisible && (
Expand Down Expand Up @@ -3871,18 +3875,24 @@ function SidebarComponent(props: any) {
</div>
)}
{chatMode !== 'ask' && !NBIAPI.config.isInClaudeCodeMode && (
<div
<button
type="button"
className={`user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`}
onClick={() => handleChatToolsButtonClick()}
title={
unsafeToolSelected
? `Tool selection can cause irreversible changes! Review each tool execution carefully.\n${toolSelectionTitle}`
: toolSelectionTitle
}
aria-label={
unsafeToolSelected
? 'Configure tools (warning: irreversible tools selected)'
: 'Configure tools'
}
>
<VscTools />
{selectedToolCount > 0 && <>{selectedToolCount}</>}
</div>
</button>
)}
{NBIAPI.config.isInClaudeCodeMode && (
<span
Expand Down
15 changes: 14 additions & 1 deletion style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,29 @@
}

/* Reset native chrome on the footer buttons that now render as <button>
so they line up visually with the older <div onClick> variants. */
so they line up visually with the older <div onClick> variants.
`line-height: 1` keeps the tools-button badge from drifting a pixel
relative to the icon when rendered inside a button vs a div. */
button.user-input-footer-button {
background: none;
border: none;
font: inherit;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
}

/* Visible focus ring for keyboard users. :focus-visible keeps it from
firing on a plain click. Matches the brand-color focus indicator
the other NBI buttons use elsewhere in this stylesheet. */
.user-input-footer-button:focus-visible {
outline: 2px solid var(--jp-brand-color1);
outline-offset: 1px;
border-radius: 3px;
color: var(--jp-ui-font-color1);
}

/* The slash-button (mentions and commands) renders text rather than an
svg, so size and center the glyph instead of relying on the svg rule. */
.user-input-footer-slash-button {
Expand Down
24 changes: 20 additions & 4 deletions ui-tests/tests/chat-sidebar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,30 @@ test.describe('chat sidebar layout', () => {
await expect(slash).toHaveText('/');
});

test('sidebar-header gear icon carries a title', async ({ page }) => {
// The gear used to ship without a title attribute while its neighbours
// had one; users hovering the icon got no affordance. Pin the title.
test('sidebar-header gear is a focusable, labelled button', async ({
page
}) => {
// Regression guard for D155 / D156: the gear used to be a <div onClick>
// so keyboard users could not reach it. Pin that it is now a real
// <button> with an aria-label, that Tab focus lands on it, and that
// pressing Enter dispatches its click handler (the settings dialog
// opens as the observable side effect).
await openChatSidebar(page);
const gear = page
.locator('.sidebar-header [title="Open Notebook Intelligence settings"]')
.locator(
'.sidebar-header button[aria-label="Open Notebook Intelligence settings"]'
)
.first();
await expect(gear).toBeVisible();
await gear.focus();
await expect(gear).toBeFocused();
await page.keyboard.press('Enter');
// The settings command opens an NBI settings widget in the main area.
// Asserting the panel's wrapper appears proves Enter dispatched the
// button's onClick handler.
await expect(page.locator('.nbi-settings-panel').first()).toBeVisible({
timeout: 5000
});
});
});

Expand Down
Loading