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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
gap: 8px;
padding: 0px 1rem;
height: 24px;
overflow: hidden;

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

/* Override inherited cursor from .positron-notebook class */
cursor: default;

/* Animate expand/collapse */
transition: height 150ms ease-out, opacity 150ms ease-out,
border-top-width 150ms ease-out, border-bottom-width 150ms ease-out;
}

.positron-notebook-code-cell-footer.collapsed {
height: 0;
opacity: 0;
border-top-width: 0;
border-bottom-width: 0;
}

@media (prefers-reduced-motion: reduce) {
.positron-notebook-code-cell-footer {
transition: none;
}
}

/* Override inherited cursor from .positron-notebook class for children spans */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PositronNotebookCodeCell } from '../PositronNotebookCells/PositronNoteb
import { formatCellDuration, formatTimestamp, getRelativeTime, isMoreThanOneHourAgo } from './cellExecutionUtils.js';
import { Icon } from '../../../../../platform/positronActionBar/browser/components/icon.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { positronClassNames } from '../../../../../base/common/positronUtilities.js';

interface CodeCellStatusFooterProps {
cell: PositronNotebookCodeCell;
Expand Down Expand Up @@ -71,13 +72,18 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
const hasCurrentSessionContent = hasExecutionResult || isCurrentlyRunning || hasTimingInfo;

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

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

const isPending = executionStatus === 'pending';

// Collapse the footer when there's no execution info to display.
// This covers cells that have never been run and cells only run in a previous session.
// isPending guard keeps the clock icon visible for queued cells.
const isCollapsed = !isPending && !hasCurrentSessionContent;

const dataExecutionStatus = executionStatus || 'idle';

const renderIcon = () => {
Expand Down Expand Up @@ -141,16 +147,10 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
return null;
};

// Build ARIA label for accessibility
// Build ARIA label for accessibility.
// Active states (running/pending) take priority over static states so a
// queued cell that has never been run is announced as "queued", not "not yet run".
const getAriaLabel = () => {
if (hasNeverBeenRun) {
return localize('cellExecution.notYetRun', 'Cell not yet run');
}

if (wasRunInPreviousSession) {
return localize('cellExecution.notRunThisSession', 'Not run this session');
}

if (isCurrentlyRunning) {
return localize('cellExecution.running', 'Cell is executing');
}
Expand All @@ -159,6 +159,14 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
return localize('cellExecution.pending', 'Cell is queued for execution');
}

if (hasNeverBeenRun) {
return localize('cellExecution.notYetRun', 'Cell not yet run');
}

if (wasRunInPreviousSession) {
return localize('cellExecution.notRunThisSession', 'Not run this session');
}

if (hasTimingInfo && duration !== undefined && lastRunEndTime !== undefined) {
const status = hasError || lastRunSuccess === false
? localize('cellExecution.failed', 'Cell execution failed')
Expand All @@ -175,9 +183,10 @@ export function CodeCellStatusFooter({ cell, hasError }: CodeCellStatusFooterPro
return (
<div
ref={containerRef}
aria-label={getAriaLabel()}
aria-hidden={isCollapsed || undefined}
aria-label={isCollapsed ? undefined : getAriaLabel()}
aria-live={isCurrentlyRunning ? 'polite' : 'off'}
className='positron-notebook-code-cell-footer'
className={positronClassNames('positron-notebook-code-cell-footer', { 'collapsed': isCollapsed })}
data-execution-status={dataExecutionStatus}
role='status'
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
border-bottom-color: transparent;
}

/* When footer is collapsed (cell hasn't been run) and no outputs, editor container needs bottom radius */
.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 {
border-bottom-left-radius: var(--vscode-positronNotebook-cell-radius);
border-bottom-right-radius: var(--vscode-positronNotebook-cell-radius);
transition: border-bottom-left-radius 150ms ease-out, border-bottom-right-radius 150ms ease-out;
}

/* Update the border color for code cells with no outputs based on the selection state */
.positron-notebook-cell.selected:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section,
.positron-notebook-cell.editing:has(.positron-notebook-outputs-section.no-outputs) .positron-notebook-editor-section {
Expand Down Expand Up @@ -201,4 +208,8 @@
animation: none;
opacity: 0;
}

.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 {
transition: none;
}
}
12 changes: 12 additions & 0 deletions test/e2e/pages/notebooksPositron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,18 @@ export class PositronNotebooks extends Notebooks {
});
}

/**
* Verify: Cell footer is collapsed (hidden via CSS animation).
* @param cellIndex - The index of the cell whose footer to check.
*/
async expectFooterNotVisible(cellIndex: number): Promise<void> {
await test.step(`Expect cell footer to be collapsed`, async () => {
const footer = this.cellFooterAtIndex(cellIndex);
await expect(footer).toHaveClass(/\bcollapsed\b/);
await expect(footer).not.toBeVisible();
});
}

/**
* Verify: Cell execution status matches expected status.
* @param cellIndex - The index of the cell to check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ test.describe('Positron Notebooks: Cell Execution Footer', {
// ========================================
// Cell 4: Footer visibility based on cell state
// ========================================
await test.step('Cell 4 - Footer visibility for never-run cell', async () => {
await test.step('Cell 4 - Footer hidden for never-run cell', async () => {
// Add a new cell but don't run it
await notebooksPositron.addCodeToCell(4, 'print("never run")', { run: false });

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