Skip to content

Commit e6d27ae

Browse files
committed
feat: animate notebook cell execution footer expand/collapse
Hide the cell execution footer when a cell has never been run or was only run in a previous session. The footer collapses with a CSS transition (height, opacity, borders) and expands when the cell is queued or executed. Respects prefers-reduced-motion by disabling transitions. ARIA labels are suppressed on collapsed footers (via aria-hidden) and reordered so active states (running/pending) take priority over static states.
1 parent 2f6d162 commit e6d27ae

5 files changed

Lines changed: 64 additions & 15 deletions

File tree

src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CodeCellStatusFooter.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
gap: 8px;
1111
padding: 0px 1rem;
1212
height: 24px;
13+
overflow: hidden;
1314

1415
/* Visual separators from editor content above and outputs below */
1516
border-top: 1px solid var(--vscode-widget-border);
@@ -25,6 +26,23 @@
2526

2627
/* Override inherited cursor from .positron-notebook class */
2728
cursor: default;
29+
30+
/* Animate expand/collapse */
31+
transition: height 150ms ease-out, opacity 150ms ease-out,
32+
border-top-width 150ms ease-out, border-bottom-width 150ms ease-out;
33+
}
34+
35+
.positron-notebook-code-cell-footer.collapsed {
36+
height: 0;
37+
opacity: 0;
38+
border-top-width: 0;
39+
border-bottom-width: 0;
40+
}
41+
42+
@media (prefers-reduced-motion: reduce) {
43+
.positron-notebook-code-cell-footer {
44+
transition: none;
45+
}
2846
}
2947

3048
/* Override inherited cursor from .positron-notebook class for children spans */

src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CodeCellStatusFooter.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,18 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
7171
const hasCurrentSessionContent = hasExecutionResult || isCurrentlyRunning || hasTimingInfo;
7272

7373
// Check if cell has never been run (no execution order and no current session data)
74-
const hasNeverBeenRun = !hasExecutionOrder && !hasExecutionResult && !isCurrentlyRunning;
74+
const hasNeverBeenRun = !hasExecutionOrder && !hasCurrentSessionContent;
7575

7676
// Check if we only have execution order from previous session (no current session data)
7777
const wasRunInPreviousSession = hasExecutionOrder && !hasCurrentSessionContent;
7878

7979
const isPending = executionStatus === 'pending';
8080

81+
// Collapse the footer when there's no execution info to display.
82+
// This covers cells that have never been run and cells only run in a previous session.
83+
// isPending guard keeps the clock icon visible for queued cells.
84+
const isCollapsed = !isPending && !hasCurrentSessionContent;
85+
8186
const dataExecutionStatus = executionStatus || 'idle';
8287

8388
const renderIcon = () => {
@@ -141,16 +146,10 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
141146
return null;
142147
};
143148

144-
// Build ARIA label for accessibility
149+
// Build ARIA label for accessibility.
150+
// Active states (running/pending) take priority over static states so a
151+
// queued cell that has never been run is announced as "queued", not "not yet run".
145152
const getAriaLabel = () => {
146-
if (hasNeverBeenRun) {
147-
return localize('cellExecution.notYetRun', 'Cell not yet run');
148-
}
149-
150-
if (wasRunInPreviousSession) {
151-
return localize('cellExecution.notRunThisSession', 'Not run this session');
152-
}
153-
154153
if (isCurrentlyRunning) {
155154
return localize('cellExecution.running', 'Cell is executing');
156155
}
@@ -159,6 +158,14 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
159158
return localize('cellExecution.pending', 'Cell is queued for execution');
160159
}
161160

161+
if (hasNeverBeenRun) {
162+
return localize('cellExecution.notYetRun', 'Cell not yet run');
163+
}
164+
165+
if (wasRunInPreviousSession) {
166+
return localize('cellExecution.notRunThisSession', 'Not run this session');
167+
}
168+
162169
if (hasTimingInfo && duration !== undefined && lastRunEndTime !== undefined) {
163170
const status = hasError || lastRunSuccess === false
164171
? localize('cellExecution.failed', 'Cell execution failed')
@@ -175,9 +182,10 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
175182
return (
176183
<div
177184
ref={containerRef}
178-
aria-label={getAriaLabel()}
185+
aria-hidden={isCollapsed || undefined}
186+
aria-label={isCollapsed ? undefined : getAriaLabel()}
179187
aria-live={isCurrentlyRunning ? 'polite' : 'off'}
180-
className='positron-notebook-code-cell-footer'
188+
className={`positron-notebook-code-cell-footer${isCollapsed ? ' collapsed' : ''}`}
181189
data-execution-status={dataExecutionStatus}
182190
role='status'
183191
>

src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSelection.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@
7676
border-bottom-color: transparent;
7777
}
7878

79+
/* When footer is collapsed (cell hasn't been run) and no outputs, editor container needs bottom radius */
80+
.positron-notebook-code-cell:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section:has(.positron-notebook-code-cell-footer.collapsed) .positron-notebook-editor-container {
81+
border-bottom-left-radius: var(--vscode-positronNotebook-cell-radius);
82+
border-bottom-right-radius: var(--vscode-positronNotebook-cell-radius);
83+
transition: border-bottom-left-radius 150ms ease-out, border-bottom-right-radius 150ms ease-out;
84+
}
85+
7986
/* Update the border color for code cells with no outputs based on the selection state */
8087
.positron-notebook-cell.selected:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section,
8188
.positron-notebook-cell.editing:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section {
@@ -201,4 +208,8 @@
201208
animation: none;
202209
opacity: 0;
203210
}
211+
212+
.positron-notebook-code-cell:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section:has(.positron-notebook-code-cell-footer.collapsed) .positron-notebook-editor-container {
213+
transition: none;
214+
}
204215
}

test/e2e/pages/notebooksPositron.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,18 @@ export class PositronNotebooks extends Notebooks {
11401140
});
11411141
}
11421142

1143+
/**
1144+
* Verify: Cell footer is collapsed (hidden via CSS animation).
1145+
* @param cellIndex - The index of the cell whose footer to check.
1146+
*/
1147+
async expectFooterNotVisible(cellIndex: number): Promise<void> {
1148+
await test.step(`Expect cell footer to be collapsed`, async () => {
1149+
const footer = this.cellFooterAtIndex(cellIndex);
1150+
await expect(footer).toHaveClass(/\bcollapsed\b/);
1151+
await expect(footer).not.toBeVisible();
1152+
});
1153+
}
1154+
11431155
/**
11441156
* Verify: Cell execution status matches expected status.
11451157
* @param cellIndex - The index of the cell to check.

test/e2e/tests/notebooks-positron/notebook-cell-execution-info.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ test.describe('Positron Notebooks: Cell Execution Footer', {
8686
// ========================================
8787
// Cell 4: Footer visibility based on cell state
8888
// ========================================
89-
await test.step('Cell 4 - Footer visibility for never-run cell', async () => {
89+
await test.step('Cell 4 - Footer hidden for never-run cell', async () => {
9090
// Add a new cell but don't run it
9191
await notebooksPositron.addCodeToCell(4, 'print("never run")', { run: false });
9292

93-
// Footer should show "Not run this session" in aria-label
94-
await notebooksPositron.expectFooterAriaLabel(4, 'Cell not yet run');
93+
// Footer should not be rendered for cells that have never been run
94+
await notebooksPositron.expectFooterNotVisible(4);
9595
});
9696
});
9797
});

0 commit comments

Comments
 (0)