Skip to content

Instantly share code, notes, and snippets.

@nstrayer
Created February 5, 2026 21:01
Show Gist options
  • Select an option

  • Save nstrayer/5a548da51d6e15ba26a909e5c9ce41f1 to your computer and use it in GitHub Desktop.

Select an option

Save nstrayer/5a548da51d6e15ba26a909e5c9ce41f1 to your computer and use it in GitHub Desktop.
Show Your Work: Raw Cell Support for Positron Notebooks - Planning context for PR

Cleanup Review: Raw Cell Support

Date: 2026-02-05 Branch: positron-nb-add-raw-cells Commit reviewed: 1150081e7e (Add raw cell support to Positron Notebooks) Scope: 12 files (2 new, 10 modified)

Summary

Overall the implementation is clean. One medium-priority consistency issue was found; two low-priority items noted for awareness.

Finding 1 (Medium): Inconsistent cell insertion pattern

Problem

The "Insert Raw Cell Above/Below" menu actions in positronNotebook.contribution.ts bypass the delegation chain used by code and markdown cell insertion.

Code/markdown pattern (established):

contribution action
  -> cell.insertCodeCellAbove()           (PositronNotebookCell.ts:370)
  -> instance.insertCodeCellAndFocusContainer('above', this)  (PositronNotebookInstance.ts:1162)
  -> instance._insertCellAndFocusContainer(CellKind.Code, ...)  (PositronNotebookInstance.ts:1138)
  -> instance.addCell(type, computedIndex, false)

Raw cell pattern (current -- inconsistent):

contribution action
  -> notebook.addCell(CellKind.Code, cell.index, true, '', 'raw')   (positronNotebook.contribution.ts:687)

Specific issues

  1. Raw actions call notebook.addCell() directly with hand-computed index (cell.index or cell.index + 1) instead of delegating through _insertCellAndFocusContainer.
  2. Raw actions pass enterEditMode: true, while the shared helper passes false. This may be intentional or an oversight -- needs verification.
  3. If insertion logic changes (undo grouping, animations, focus behavior), raw cell paths won't pick up those changes.

Fix: Add insertRawCell* methods to match code/markdown pattern

File 1: IPositronNotebookCell.ts -- add after insertMarkdownCellBelow() (line 126):

/**
 * Insert a new raw cell above this cell
 */
insertRawCellAbove(): void;

/**
 * Insert a new raw cell below this cell
 */
insertRawCellBelow(): void;

File 2: IPositronNotebookInstance.ts -- add after insertMarkdownCellAndFocusContainer (line 275):

/**
 * Inserts a new raw cell either above or below the current selection
 * and focuses the container.
 *
 * @param aboveOrBelow Whether to insert the cell above or below the current selection
 * @param referenceCell Optional cell to insert relative to. If not provided, uses the currently selected cell
 */
insertRawCellAndFocusContainer(aboveOrBelow: 'above' | 'below', referenceCell?: IPositronNotebookCell): void;

File 3: PositronNotebookInstance.ts -- thread language through private helper and add public method:

Update _insertCellAndFocusContainer (line 1138) to accept an optional language parameter:

private _insertCellAndFocusContainer(type: CellKind, aboveOrBelow: 'above' | 'below', referenceCell?: IPositronNotebookCell, language?: string): void {
    let index: number | undefined;

    this._assertTextModel();

    if (referenceCell) {
        const cellIndex = referenceCell.index;
        index = cellIndex >= 0 ? cellIndex : undefined;
    } else {
        index = getActiveCell(this.selectionStateMachine.state.get())?.index;
    }

    if (index === undefined) {
        return;
    }

    this.addCell(type, index + (aboveOrBelow === 'above' ? 0 : 1), false, '', language);
}

Add after insertMarkdownCellAndFocusContainer (line 1168):

insertRawCellAndFocusContainer(aboveOrBelow: 'above' | 'below', referenceCell?: IPositronNotebookCell): void {
    this._insertCellAndFocusContainer(CellKind.Code, aboveOrBelow, referenceCell, 'raw');
}

File 4: PositronNotebookCell.ts -- add after insertMarkdownCellBelow() (line 384):

insertRawCellAbove(): void {
    this._instance.insertRawCellAndFocusContainer('above', this);
}

insertRawCellBelow(): void {
    this._instance.insertRawCellAndFocusContainer('below', this);
}

File 5: positronNotebook.contribution.ts -- simplify raw cell actions:

Replace lines 683-689 (Insert Raw Cell Above action body):

override runNotebookAction(notebook: IPositronNotebookInstance, _accessor: ServicesAccessor) {
    const state = notebook.selectionStateMachine.state.get();
    const cell = getActiveCell(state);
    if (cell) {
        cell.insertRawCellAbove();
    }
}

Replace lines 708-714 (Insert Raw Cell Below action body):

override runNotebookAction(notebook: IPositronNotebookInstance, _accessor: ServicesAccessor) {
    const state = notebook.selectionStateMachine.state.get();
    const cell = getActiveCell(state);
    if (cell) {
        cell.insertRawCellBelow();
    }
}

Open question

The current raw cell actions pass enterEditMode: true to addCell(). The shared _insertCellAndFocusContainer passes false. Verify which is correct for raw cells before applying this fix. If raw cells should enter edit mode on insert, the _insertCellAndFocusContainer helper may need an additional parameter, or the raw cell path can set it separately.


Finding 2 (Low): Minor CSS overlap

NotebookRawCell.css lines 11-17 define .positron-notebook-editor-section flex layout rules that partially overlap with NotebookCodeCell.css. The differences are justified by the editor-only layout, so no action needed now. If more cell types are added, consider extracting a shared base .positron-notebook-cell-contents class.

Finding 3 (Low): Inline compound type check

NotebookCellWrapper.tsx:69 uses cell.isCodeCell() || cell.isRawCell() to mean "cell has a persistent editor." Currently appears only once. If this pattern appears in more places, add an isEditableCell() helper to the cell interface. No action needed now -- just monitor.


What was NOT found (positive observations)

  • No significant code duplication between NotebookRawCell.tsx and existing cell components
  • Type guards (isRawCell, isCodeCell, isMarkdownCell) are correctly mutually exclusive
  • Cell routing in PositronNotebookComponent.tsx is correctly ordered (raw before code)
  • Execution filter in notebookExecutionServiceImpl.ts is in the right architectural location
  • NotebookRawCell.tsx correctly reuses NotebookCellWrapper and CellEditorMonacoWidget
date researcher git_commit branch repository topic tags status last_updated last_updated_by
2026-02-05 10:55:15 -0500
research_codebase
b567273fab5f260a8bbb5a3e8ba5d49fe57932f4
positron-nb-add-raw-cells
positron-nb-add-raw-cells
How Positron notebooks handle cell types and implementing raw cells
research
codebase
notebooks
cells
raw-cells
jupyter
complete
2026-02-05
research_codebase

Research: How Positron notebooks handle cell types and implementing raw cells

Date: 2026-02-05T10:55:15-05:00 Researcher: research_codebase Git Commit: b567273fab5f260a8bbb5a3e8ba5d49fe57932f4 Branch: positron-nb-add-raw-cells Repository: positron-nb-add-raw-cells

Research Question

How do Positron notebooks handle different cell types and what would go into adding raw cells as a new type? What are raw cells and why do they exist?

Summary

Positron notebooks currently implement a two-value cell type system (CellKind.Markup and CellKind.Code) where "raw" cells are represented as code cells with language='raw' rather than having a distinct cell kind. Raw cells in Jupyter notebooks are a special cell type designed to hold content that passes through notebook conversion unmodified, useful for format-specific content like LaTeX, reStructuredText, or document metadata.

To properly support raw cells in Positron, the implementation would need to:

  1. Maintain the current approach of using CellKind.Code with language='raw' for compatibility
  2. Add raw cell-specific rendering that displays content without code execution capabilities
  3. Ensure proper serialization/deserialization with the ipynb format
  4. Add UI elements and keyboard shortcuts for creating and converting raw cells

Detailed Findings

What Are Raw Cells?

Raw cells are a fundamental Jupyter notebook cell type designed to contain content that should pass through notebook conversion processes unmodified. Unlike code cells (which execute) and markdown cells (which render formatted text), raw cells preserve their exact content for use in downstream export formats.

Key characteristics:

  • Not executed by the notebook kernel
  • Not rendered as formatted text in the notebook interface
  • Pass through nbconvert unchanged
  • Can optionally target specific output formats via metadata

Common use cases:

  1. LaTeX content for PDF export: Include raw LaTeX commands that only make sense when converting to PDF
  2. reStructuredText for Sphinx: Embed RST directives for Sphinx documentation builds
  3. Document metadata: Store YAML front matter for document configuration (Quarto/MyST)
  4. Format-specific HTML: Include HTML that should pass through unchanged
  5. Custom export formats: Any content intended for a specific output format

Current Cell Type Architecture

Core Cell Type Definition

The fundamental cell type enum is defined in /Users/nicholasstrayer/dev/positron-work/positron.worktrees/positron-nb-add-raw-cells/src/vs/workbench/contrib/notebook/common/notebookCommon.ts:49-52:

export enum CellKind {
    Markup = 1,
    Code = 2
}

Positron maintains a local copy at /Users/nicholasstrayer/dev/positron-work/positron.worktrees/positron-nb-add-raw-cells/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/IPositronNotebookCell.ts:291-294 to avoid import issues.

Cell Class Hierarchy

  • Base class: PositronNotebookCellGeneral (PositronNotebookCell.ts:41)
    • Declares abstract readonly kind: CellKind
    • Provides type guard methods isMarkdownCell() and isCodeCell()
  • Code cell: PositronNotebookCodeCell (PositronNotebookCodeCell.ts:20)
    • Sets kind: CellKind.Code
    • Handles execution, outputs, and status
  • Markdown cell: PositronNotebookMarkdownCell (PositronNotebookMarkdownCell.ts:16)
    • Sets kind: CellKind.Markup
    • Manages editor visibility and markdown rendering

Cell Creation Factory

The factory function at createNotebookCell.ts:21 creates appropriate cell instances:

export function createNotebookCell(cell: NotebookCellTextModel, instance: PositronNotebookInstance, instantiationService: IInstantiationService): IPositronNotebookCell {
    if (cell.cellKind === CellKind.Code) {
        return instantiationService.createInstance(PositronNotebookCodeCell, cell, instance);
    } else {
        return instantiationService.createInstance(PositronNotebookMarkdownCell, cell, instance);
    }
}

Current Raw Cell Implementation

Raw cells are currently implemented as code cells with language='raw':

Detection via Context Keys

useCellContextKeys.ts:71-74:

const cellType = cell.kind;
keys.isCode.set(cellType === CellKind.Code);
keys.isMarkdown.set(cellType === CellKind.Markup);
keys.isRaw.set(cellType === CellKind.Code && cell.model.language === 'raw');

Cell Type Conversion Action

positronNotebook.contribution.ts:1259-1287:

// Change to Raw cell - r key (Jupyter-style)
override runNotebookAction(notebook: IPositronNotebookInstance, _accessor: ServicesAccessor) {
    notebook.changeCellType(CellKind.Code, 'raw');
}

ipynb Format Handling

Deserialization (deserializers.ts:310-315):

function createNotebookCellDataFromRawCell(cell: nbformat.IRawCell): NotebookCellData {
    const cellData = new NotebookCellData(NotebookCellKind.Code, concatMultilineCellSource(cell.source), 'raw');
    cellData.outputs = [];
    cellData.metadata = getNotebookCellMetadata(cell);
    return cellData;
}

Serialization (serializers.ts:13-26):

if (vscCell.kind === NotebookCellKindMarkup) {
    cell = createMarkdownCellFromNotebookCell(vscCell);
} else if (vscCell.languageId === 'raw') {
    cell = createRawCellFromNotebookCell(vscCell);  // Creates cell_type: 'raw'
} else {
    cell = createCodeCellFromNotebookCell(vscCell, preferredLanguage);
}

Cell Type Switching Mechanism

The changeCellType method (PositronNotebookInstance.ts:1175-1258) handles cell type conversion:

  1. For cell kind changes: Uses CellEditType.Replace to create a new cell with the target kind
  2. For language-only changes: Uses CellEditType.CellLanguage to update just the language
  3. Preserves: Content, metadata, and outputs during conversion

UI and Rendering

Component Routing

PositronNotebookComponent.tsx:200-213:

function NotebookCell({ cell }: { cell: PositronNotebookCellGeneral }) {
    if (cell.isCodeCell()) {
        return <NotebookCodeCell cell={cell} />;
    }
    if (cell.isMarkdownCell()) {
        return <NotebookMarkdownCell cell={cell} />;
    }
    throw new Error('Unknown cell type');
}

Currently, raw cells (being code cells with language='raw') are rendered using the NotebookCodeCell component, which includes execution controls and output areas that aren't appropriate for raw cells.

Architecture Documentation

Key Design Patterns

  1. Two-Value Cell Kind System: Only Markup and Code exist in the core enum, with raw cells represented via language differentiation
  2. Factory Pattern: Cell creation delegated to factory function based on CellKind
  3. Type Guard Pattern: isCodeCell() and isMarkdownCell() methods for type-safe narrowing
  4. Context Key Pattern: Cell type exposed via VS Code context keys for menu/command availability
  5. Observer Pattern: Cell state exposed via observables for reactive UI updates

Data Flow

  1. Reading notebooks: ipynb → createNotebookCellDataFromJupyterCell → Maps cell_type to NotebookCellKind
  2. Creating instances: createNotebookCell factory → Creates appropriate cell class
  3. Rendering: React component checks type guards → Routes to appropriate component
  4. Type switching: User command → changeCellType() → Model update via edits
  5. Saving: Cell instances → createJupyterCellFromNotebookCell → ipynb format

What Would Be Required to Add Proper Raw Cell Support

Based on the current architecture, adding proper raw cell support would require:

1. Maintain Current Data Model

  • Keep using CellKind.Code with language='raw' for compatibility
  • This avoids breaking changes to the core VS Code notebook model

2. Add Raw Cell-Specific UI Component

Create a new component at src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookRawCell.tsx:

  • Display cell content without execution controls
  • No output section
  • Appropriate visual styling to differentiate from code/markdown
  • Optional metadata display for target format

3. Update Component Routing

Modify PositronNotebookComponent.tsx to check for raw cells:

function NotebookCell({ cell }: { cell: PositronNotebookCellGeneral }) {
    if (cell.isCodeCell()) {
        // Check if it's a raw cell
        if (cell.model.language === 'raw') {
            return <NotebookRawCell cell={cell} />;
        }
        return <NotebookCodeCell cell={cell} />;
    }
    if (cell.isMarkdownCell()) {
        return <NotebookMarkdownCell cell={cell} />;
    }
    throw new Error('Unknown cell type');
}

4. Add Cell Creation UI

5. Ensure Proper Serialization

  • Verify ipynb serialization/deserialization handles raw cells correctly (appears to already work)
  • Test round-trip conversion preserves raw cell content and metadata

6. Add Tests

  • Unit tests for raw cell creation and type conversion
  • E2E tests for UI interactions
  • ipynb format round-trip tests

7. Optional Enhancements

  • Support for format metadata field to target specific export formats
  • Visual indicators for target format
  • Syntax highlighting based on content type

Code References

Key files for implementation:

  • src/vs/workbench/contrib/notebook/common/notebookCommon.ts:49 - Core CellKind enum
  • src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/createNotebookCell.ts:21 - Cell factory
  • src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts:1175 - changeCellType method
  • src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx:200 - Component routing
  • src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts:1259 - Raw cell action
  • src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useCellContextKeys.ts:74 - Raw cell detection
  • extensions/ipynb/src/deserializers.ts:310 - ipynb raw cell deserialization
  • extensions/ipynb/src/serializers.ts:118 - ipynb raw cell serialization

Historical Context (from thoughts/)

No existing documentation was found in the thoughts/ directory regarding:

  • Raw cell implementation decisions
  • Cell type architecture choices
  • Jupyter compatibility considerations

All notebook-related documentation focuses on the "ghost cell" AI suggestion feature and notebook-specific settings.

Related Research

None found in thoughts/shared/research/ regarding notebook cell types or raw cells.

Open Questions

  1. Should raw cells have any special behaviors or features beyond basic text display?
  2. Should the format metadata field be exposed in the UI for targeting specific export formats?
  3. Should raw cells support any form of syntax highlighting based on their intended format?
  4. How should raw cells be visually distinguished from code and markdown cells in the UI?

Task: Raw Cell Support for Positron Notebooks

Status: in-progress Last Updated: 2026-02-05

Context for Claude

When working with this task, keep this file updated:

  • Current State: Update when features/components are completed
  • Decisions Made: Add when you choose between approaches (include why)
  • Key Files: Add files you discover that are central but weren't listed
  • Gap detection: If you had to look something up that should have been documented here, add it immediately

Keep updates concise--bullet points, not paragraphs.

Overview

Adding proper raw cell support to Positron notebooks. Raw cells (Jupyter's third cell type) are already stored correctly in VS Code's data model (CellKind.Code with language='raw'), but previocanusly rendered using the code cell component with inappropriate execution controls. This adds a dedicated UI component, type guards, and menu options.

Key Files

  • PositronNotebookCells/IPositronNotebookCell.ts - Defines IPositronNotebookRawCell interface and isRawCell() type guard on the cell interface
  • PositronNotebookCells/PositronNotebookCell.ts - Implements isRawCell() and updated isCodeCell() to exclude raw cells
  • notebookCells/NotebookRawCell.tsx - NEW: Raw cell React component (Monaco editor, no execution controls)
  • notebookCells/NotebookRawCell.css - NEW: Minimal styling for raw cells
  • notebookCells/NotebookCellWrapper.tsx - Updated CSS class, aria-label, and focus logic to handle raw cell type
  • notebookCells/useCellContextKeys.ts - Updated to use type guard methods instead of raw kind/language checks
  • PositronNotebookComponent.tsx - Cell routing: raw cell check added before code cell check
  • positronNotebook.contribution.ts - "Insert Raw Cell Above/Below" menu actions added
  • PositronNotebookInstance.ts / IPositronNotebookInstance.ts - addCell() gained optional language parameter for raw cell creation
  • notebookExecutionServiceImpl.ts - Execution filter updated to exclude raw cells (c.language !== 'raw')

Decisions Made

  • No separate runtime class for raw cells: Raw cells reuse PositronNotebookCodeCell at runtime, differentiated only by type guards. Avoids unnecessary class hierarchy complexity.
  • isCodeCell() returns false for raw cells: Makes the three type guards mutually exclusive. All existing isCodeCell() callers were execution-related, so this was safe. Focus logic in NotebookCellWrapper explicitly checks isCodeCell() || isRawCell().
  • No empty-cell placeholder text: The plan proposed using cell.textBuffer which doesn't exist. Skipped placeholder for simplicity; can be added later if needed.
  • No primary "Add Raw Cell" button: Raw cells are accessible only via submenu and context menu, not the top-level action bar. Per requirements.
  • Language parameter on addCell(): Added as optional 5th parameter (simple approach) rather than a dedicated addRawCell() method or options object refactor.

Current State

  • Phases 1-3 complete: Type guards, raw cell component, cell routing, menu actions, execution filtering all implemented
  • TypeScript compiles clean with 0 errors
  • Cleanup review complete: One medium-priority consistency issue identified (see next steps)
  • Phase 4 (E2E tests) not started: Plan includes test specs but they need adaptation to actual test infrastructure
  • Manual testing not yet done: Needs verification of rendering, focus, menu options, Run All behavior

Next Steps

1. Fix inconsistent raw cell insertion pattern (medium priority)

Raw cell "Insert Above/Below" actions bypass the delegation chain used by code/markdown cells. They call notebook.addCell() directly instead of going through cell helper methods and instance methods. This means future changes to insertion logic (undo grouping, focus behavior, animations) won't apply to raw cells.

What to do: Add insertRawCellAbove/Below methods following the same pattern as insertCodeCellAbove/Below.

Detailed fix instructions: thoughts/shared/research/2026-02-05-raw-cells-cleanup-review.md -- Finding 1. Contains exact code to add in each of the 5 files.

Files to modify:

  1. PositronNotebookCells/IPositronNotebookCell.ts -- add insertRawCellAbove() and insertRawCellBelow() to interface
  2. IPositronNotebookInstance.ts -- add insertRawCellAndFocusContainer() to interface
  3. PositronNotebookInstance.ts -- add language param to _insertCellAndFocusContainer(), add insertRawCellAndFocusContainer() implementation
  4. PositronNotebookCells/PositronNotebookCell.ts -- add insertRawCellAbove() and insertRawCellBelow() implementations
  5. positronNotebook.contribution.ts -- simplify raw cell action bodies to use cell.insertRawCellAbove() / cell.insertRawCellBelow()

Open question to resolve first: Current raw cell actions pass enterEditMode: true to addCell(). The shared helper passes false. Determine which is correct for raw cells before applying.

2. Manual testing of Phases 1-3

Verify rendering, focus, menu options, and Run All behavior with raw cells present.

3. Phase 4: E2E tests

Plan includes test specs but they need adaptation to actual test infrastructure. Load the positron-e2e-tests skill for guidance.

Related Docs

  • thoughts/shared/plans/2026-02-05-raw-cells-implementation.md - Full implementation plan with phase details
  • thoughts/shared/research/2026-02-05-raw-cells-implementation.md - Research document on raw cell architecture
  • thoughts/shared/research/2026-02-05-raw-cells-cleanup-review.md - Cleanup review with detailed fix for insertion pattern inconsistency
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment