<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - JupyterLab</title><link href="https://jasongrout.github.io/medium-archive/pelican/" rel="alternate"/><link href="https://jasongrout.github.io/medium-archive/pelican/feeds/tag-jupyterlab.atom.xml" rel="self"/><id>https://jasongrout.github.io/medium-archive/pelican/</id><updated>2026-07-02T16:15:00+00:00</updated><subtitle>The Project Jupyter blog: news, releases, and community stories, archived from blog.jupyter.org.</subtitle><entry><title>JupyterLab 4.6 and Notebook 7.6 are out!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/" rel="alternate"/><published>2026-07-02T16:13:00+00:00</published><updated>2026-07-02T16:15:00+00:00</updated><author><name>Michał Krassowski</name></author><id>tag:jasongrout.github.io,2026-07-02:/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/</id><summary type="html">&lt;p&gt;JupyterLab 4.6 features new interface customization options, improved file browser and debugger, an easy way to jump between recently…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://jupyterlab.readthedocs.io/en/latest/getting_started/changelog.html#v4-6"&gt;JupyterLab 4.6&lt;/a&gt; features new interface customization options, improved file browser and debugger, an easy way to jump between recently edited cells, and numerous other notebook experience improvements. Keyboard shortcuts, navigation and accessibility is another focus area in this release. &lt;a href="https://jupyter-notebook.readthedocs.io/en/stable/changelog.html#id1"&gt;Jupyter Notebook 7.6&lt;/a&gt; has also been released, including the fixes and enhancements of JupyterLab along with a new Scratchpad console and other Notebook-specific improvements. This article highlights notable changes in both applications.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can try out the changes in your browser: &lt;a href="https://jupyter.org/try-jupyter/lab/index.html"&gt;JupyterLab 4.6&lt;/a&gt;, &lt;a href="https://jupyter.org/try-jupyter/notebooks/?path=notebooks/Intro.ipynb"&gt;Notebook 7.6&lt;/a&gt;&lt;br&gt;
At the time of publication, the preview runs on &lt;a href="https://github.com/jupyterlite/jupyterlite"&gt;JupyterLite&lt;/a&gt; 0.8.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;img alt="Overview of some user interface enhancements visible in JupyterLab UI" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/001-1_zni6m_lgsXqu-zsGH77zBw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Overview of some user interface enhancements visible in JupyterLab UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="enhancements-shared-by-jupyterlab-and-notebook"&gt;Enhancements shared by JupyterLab and Notebook&lt;/h2&gt;
&lt;h3 id="notebook-improvements"&gt;Notebook improvements&lt;/h3&gt;
&lt;p&gt;Two new navigation commands, “Select previous last modified cell” and “Select next last modified cell”, allow jumping back and forward through recently edited cells. The commands are accessible from the Command Palette and as buttons in the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/toc.html"&gt;Table Of Contents&lt;/a&gt; toolbar when a notebook is active.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Clicking the highlighted button will jump to a previously edited cell" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/002-0_3gi9bAn6k4-yH8MP.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Clicking the highlighted button will jump to a previously edited cell&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Copy, cut, and paste text commands have been added to the notebook context menu. This feature requires permission to access clipboard to work reliably and may not work in Firefox depending on version and additional restrictions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copy/Cut/Paste Selected cells expose clipboard actions on cell source" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/003-0_fuYCAQUXQwuqp6cG.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copy/Cut/Paste Selected cells expose clipboard actions on cell source&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The cell toolbar delete button now shows a confirmation dialog to prevent accidental deletion. The “Do not ask me again” checkbox is available and the preference is persisted in &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/notebook.html#cell-toolbar"&gt;Cell Toolbar&lt;/a&gt; settings. The standard keyboard shortcut (D, D) is unaffected.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Dialog shown when deleting a cell from cell tollbar" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/004-0_vITL_K8lFE_UIsHj.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Dialog shown when deleting a cell from cell tollbar&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Pressing Ctrl + B (Cmd + B on macOS) in a markdown cell wraps the selected text in bold formatting.&lt;/p&gt;
&lt;p&gt;When exporting a notebook as HTML via &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/export.html"&gt;File → Save and Export Notebook As → HTML&lt;/a&gt;, a dialog now asks whether to sanitize the HTML output before download.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="file-browser-new-column-editable-breadcrumbs-open-in-terminal"&gt;File browser: new column, editable breadcrumbs, open in terminal&lt;/h3&gt;
&lt;p&gt;A new “Date Created” column has been added to the file browser, showing when files were created. The column can be toggled via Settings → File Browser → “Show date created column” or by right-clicking the column header. The latest &lt;code&gt;jupyter-server&lt;/code&gt; (&lt;code&gt;v2.18&lt;/code&gt;+) is required for accurate values across operating systems.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="New date created column in file browser" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/005-0_epZrkZYQDIzropSt.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;New date created column in file browser&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The breadcrumb bar now supports &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/files.html#editable-breadcrumbs"&gt;direct path editing&lt;/a&gt; with tab-completion. Clicking the area behind breadcrumbs (highlighted on hover), or using the Command Palette, opens an editable text field where pressing Tab completes the longest common prefix of matching subdirectories.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Editable breadcrumbs" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/006-0_grxuAxyEQqLFVn6d.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Editable breadcrumbs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A new “Open in Terminal” option in the file browser context menu opens a terminal navigated to the selected directory. When multiple directories are selected, one terminal is opened per directory.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Opening terminal for a specific directory is now easier." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/007-0_aespdpw35miy6vmr.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Opening terminal for a specific directory is now easier.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Additional file browser improvements include: a configurable &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/files.html#file-name-sort-order"&gt;file name sort order&lt;/a&gt;, persistence of the sort state across sessions, automatic clearing of the file filter when changing directories, and a loading animation during file browser refresh.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="debugger-kernel-sources-filter-floating-overlay"&gt;Debugger: kernel sources filter, floating overlay&lt;/h3&gt;
&lt;p&gt;The debugger Sources panel has been moved out of the debugger sidebar; the Sources now open as read-only editors in the main area, keeping the sidebar uncluttered while you &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/debugger.html#explore-the-code-state"&gt;explore the code state&lt;/a&gt;. The previous behavior can be restored by disabling the “Show Sources in Main Area” setting in the Debugger settings.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/008-0_l0V_AJYDsYfEOA54.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;An overlay with continue/step in/stop buttons has been added easing the control of the debugger steps:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/009-0_-kwAopUu6uow4ERY.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The Kernel Sources filter bar has been moved to the toolbar and now supports live filtering, making it easier to search through kernel source files while debugging.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/010-0_lXUep59E1svw80P6.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The refresh button for sources list was removed — the sources will now update automatically after each code execution.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="keyboard-navigation-and-accessibility"&gt;Keyboard navigation and accessibility&lt;/h3&gt;
&lt;p&gt;Keyboard navigation has been improved across several components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The terminal no longer traps keyboard focus, allowing users to move focus away with the keyboard.&lt;/li&gt;
&lt;li&gt;The console and file browser breadcrumbs both received keyboard navigation fixes.&lt;/li&gt;
&lt;li&gt;Focus is now correctly restored after closing the command palette, and input elements in dialogs opened from the command palette receive focus immediately.&lt;/li&gt;
&lt;li&gt;The find-and-replace button focus in the document search box has been corrected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Screen reader behavior has been improved in the launcher and notebook. Toolbar buttons now correctly reflect their pressed state via the &lt;code&gt;aria-pressed&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;The Keyboard Shortcuts settings panel has been updated to improve accessibility, including better keyboard interaction and screen reader announcements within the shortcuts editor dialog.&lt;/p&gt;
&lt;p&gt;Focus indicators have been improved across the interface, including the status bar and other interactive components.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="keyboard-shortcuts"&gt;Keyboard shortcuts&lt;/h3&gt;
&lt;p&gt;Shortcuts can now be added from the UI shortcut editor for any command (previously this required writing JSON in Advanced Settings Editor).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/011-0_JYHGSldDZ4bsNCVk.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;“Find and Replace” has been added to the Edit menu with the keyboard shortcut Ctrl + H (Cmd + H on macOS).&lt;/p&gt;
&lt;p&gt;Ctrl + Y has been added as a redo shortcut on Windows and Linux.&lt;/p&gt;
&lt;p&gt;Pressing 1, 2, 3, 4, 5 and 6 will no longer convert a focused code or raw cell to Markdown to avoid accidental conversions. These shortcuts continue working on markdown cells, switching the heading level, as they did before.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="internationalization"&gt;Internationalization&lt;/h3&gt;
&lt;p&gt;Language packs can now be installed directly from within JupyterLab by selecting “Install more languages…” at the bottom of the Settings → Language menu. This opens the Extension Manager pre-filled with a search for available language pack extensions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/012-0_IAY1NHv2eUZlhsoQ.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="terminal-enhancements"&gt;Terminal enhancements&lt;/h3&gt;
&lt;p&gt;Pressing Shift+Enter in the terminal now inserts a newline without executing the current line. This matches the behavior expected by certain terminal applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="inline-completion-enhancements"&gt;Inline completion enhancements&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/completer.html#inline-completer"&gt;Inline completion suggestions&lt;/a&gt; can now be rendered with syntax highlighting that matches the active editor language. This can be enabled in Settings → Inline Completer → “Ghost text syntax highlighting”.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/013-0_eUvIfOASnsZ0RTSH.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Ghost text is now also shown for all active cursors in a multi-cursor editing session.&lt;/p&gt;
&lt;h2 id="jupyterlab-specific-enhancements"&gt;JupyterLab-specific enhancements&lt;/h2&gt;
&lt;h3 id="layout-and-interface-customization"&gt;Layout and interface customization&lt;/h3&gt;
&lt;p&gt;The activity bar can now be positioned at the top or bottom of the interface, in addition to the default left and right placement. The setting is available under Settings → Interface → &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/interface_customization.html#activity-bar-position"&gt;Activity Bar Position&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/014-0_FZnWSzWq9TsSfnYH.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Sections of accordion sidebars can now be &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/interface_customization.html#move-accordion-sections-between-panels"&gt;moved between panels&lt;/a&gt; via a context menu. Right-clicking a section header shows a “Move To” menu. The placement and collapsed state of moved sections are persisted across sessions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/015-0_EaVBoUQh5fMYTOCs.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;br&gt;
&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/016-0_cGeR3SdD_1GL4pcS.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The sidebar panels themselves can be &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/interface_customization.html#move-open-widgets"&gt;moved between areas&lt;/a&gt;. Right-clicking a sidebar shows submenu with options to move it to the main area, a different sidebar, or the down area.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/017-0_vZPh7LgFw5VKY96R.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;br&gt;
&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/018-0_tqASYUG9UE6LoV2y.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Tabs can now be split in four directions using the “Split Tab” context menu item (or the command palette), which adds split left, right, up, and down options.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/019-0_7qxAk2wzTtYyiR7c.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Individual panels can be zoomed in or out using Ctrl + mouse wheel (when “Enable Ctrl+ Scroll Zoom” is turned on in Settings → Interface) or via the command palette, without affecting other panels.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/020-0_COyiDcTCqgm8mJcD.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="performance"&gt;Performance&lt;/h3&gt;
&lt;p&gt;Panel resizing is now more responsive; the width of individual widgets inside resized panels is frozen during the drag. This behaviour can be disabled by toggling “Optimize panel resize” in Settings → Application Shell.&lt;/p&gt;
&lt;h2 id="notebook-specific-enhancements"&gt;Notebook-specific enhancements&lt;/h2&gt;
&lt;h3 id="scratchpad-console"&gt;Scratchpad console&lt;/h3&gt;
&lt;p&gt;A scratchpad console can now be opened next to a notebook, sharing the same kernel. This makes it easy to run quick experiments or inspect variables without modifying the notebook itself.&lt;/p&gt;
&lt;p&gt;The scratchpad console can be opened from the &lt;code&gt;File&lt;/code&gt; -&amp;gt; &lt;code&gt;New&lt;/code&gt; -&amp;gt; &lt;code&gt;Scratchpad console&lt;/code&gt; menu, from the command palette, or with the Ctrl + B (or Cmd + B on macOS) keyboard shortcut, which toggles the console panel.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/021-1_cxBr835uxAtjpO9Tlqg7bw.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="confirmation-dialog-when-closing-and-shutting-down-a-notebook"&gt;Confirmation dialog when closing and shutting down a notebook&lt;/h3&gt;
&lt;p&gt;The “Close and Shut Down Notebook” command now asks for confirmation before closing the browser tab and shutting down the kernel. The confirmation prompt can be disabled in the Settings Editor with the “Prompt for confirmation before closing and shutting down” setting.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/jupyterlab-4-6-and-notebook-7-6-are-out/images/022-1_hWEaY7HfHBjzbajj9OIKtQ.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tab widths in JupyterLab are kept frozen while the mouse pointer hovers over them, making it easier to close multiple tabs with the mouse&lt;/li&gt;
&lt;li&gt;Autocompletion heuristics were improved; the autocompletion (which remains opt-in) should no longer trigger in unexpected scenarios&lt;/li&gt;
&lt;li&gt;The output scroll overlay collapse icon is now visible regardless of the output size and scroll state&lt;/li&gt;
&lt;li&gt;The fonts used across components were standardized and fonts specified by themes or overrides are respected in every component of the application&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="for-extension-authors"&gt;&lt;strong&gt;For extension authors&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Building extensions (and JupyterLab/Notebook themselves) is now ~5 times faster thanks to migration from webpack to Rspack. Building extensions no longer requires a full JupyterLab installation as the relevant machinery was extracted to &lt;a href="https://github.com/jupyterlab/jupyter-builder"&gt;jupyter-builder&lt;/a&gt; (see this &lt;a href="/posts/2026/announcing-jupyter-builder-a-standalone-build-system/"&gt;blog post&lt;/a&gt; for more details). This (among others) enables faster build environment setup on CI. Future improvements to the build system can now be released independently of the JupyterLab release cadence.&lt;/p&gt;
&lt;p&gt;Please refer to the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#jupyterlab-4-5-to-4-6"&gt;Extension Migration Guide&lt;/a&gt; for more details on API changes and up-to-date advice.&lt;/p&gt;
&lt;h2 id="as-large-as-minor-releases-go"&gt;As large as minor releases go&lt;/h2&gt;
&lt;p&gt;This JupyterLab release includes 68 new features and enhancements, 97 bug fixes, and 38 documentation improvements. 95 people contributed to this release. Across record-breaking 171 maintenance tasks we kept the dependencies up-to-date, improved quality of code and reduced the time to PRs getting merged by improving the CI performance and robustness. These efforts were in part funded by &lt;a href="https://jupyterfoundation.org/"&gt;Jupyter Foundation&lt;/a&gt; &lt;a href="https://jupyterfoundation.org/community-funding-proposals/"&gt;Community Proposals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While the size of minor releases fluctuated across years, this one is on the higher side, with only v3.1 featuring slightly more merged PRs. The reviewers worked hard to ensure no regressions are introduced, but if any slipped through, please open an issue in respective repository (&lt;a href="https://github.com/jupyterlab/jupyterlab/issues/new/choose"&gt;JupyterLab&lt;/a&gt; or &lt;a href="https://github.com/jupyter/notebook/issues/new/choose"&gt;Notebook&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thank you to all the new contributors, we hope to see you back!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab (39): @aarondr77, @Akshitguptaa, @altis0725, @amanparganiha, @anafontan, @autodots-AHassan, @aviu16, @bittoby, @boskodev790, @chenjiahan, @cursoragent, @dhgoal, @DTiming24, @github29tani, @GlassOfWhiskey, @harshaagrawal20, @hroncok, @jayaprajapatii, @JaYRaNa213, @jianyexi, @ktaletsk, @lin9long, @mfisher87, @MMesch, @moktamd, @nightcityblade, @nthiery, @raffaelemancuso, @RamiNoodle733, @reteps, @rodrigosf672, @Siddhazntx, @statxc, @TheChyeahhh, @tsharma-dev, @tubaman, @u-wlkjyy, @Yahiewi, @ZhYGuoL&lt;/li&gt;
&lt;li&gt;Notebook (9): @bodhisilberling, @Darshan808, @goelakash, @joaovml, @KH-Coder865, @Mirochill, @xlumzee, @yangsong97, @Yann-P&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And thank you to returning contributors for their continued work!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab (56): @aaron-seq, @afshin, @agriyakhetarpal, @AliMahmoudDev, @andrii-i, @apoorvdarshan, @arjxn-py, @arun-357, @bollwyvl, @brichet, @Carreau, @ceasermikes002, @cmarmo, @CrafterKolyan, @Darshan808, @davidbrochart, @DeborahOlaboye, @doshi-kevin, @erkin98, @fcollonval, @filipeoliveira05, @flaviomartins, @fleming79, @FoSuCloud, @frenzymadness, @HaudinFlorence, @i-aki-y, @ianhi, @ianthomas23, @IsabelParedes, @itsmejay80, @itsmevichu, @JamesWrigley, @jasongrout, @JasonWeill, @jimangel, @jtpio, @krassowski, @Krish-876, @linlol, @martinRenou, @Meriem-BenIsmail, @MUFFANUJ, @peytondmurray, @prajyot-porje, @pranshugupta01, @RRosio, @schmidi314, @soniya-malviy, @superlevure, @SylvainCorlay, @tmetzl, @utsav-develops, @xc2, @Yann-P, @Zsailer&lt;/li&gt;
&lt;li&gt;Notebook (9): @andrii-i, @brichet, @Carreau, @jtpio, @krassowski, @martinRenou, @minrk, @RamiNoodle733, @takluyver&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to all the people who tested and provided feedback for the pre-releases! Lastly, thank you to the weekly triage group for triaging JupyterLab and Notebook issues: @RRosio, @krassowski, @andrii-i, @rodrigosf672, @ktaletsk among others who attend.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;
&lt;p&gt;For more details, see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Release Notes for &lt;a href="https://jupyterlab.readthedocs.io/en/latest/getting_started/changelog.html#v4-6"&gt;JupyterLab 4.6&lt;/a&gt; and &lt;a href="https://jupyter-notebook.readthedocs.io/en/stable/changelog.html#id1"&gt;Notebook 7.6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Full changelog on GitHub: &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.6.0"&gt;JupyterLab&lt;/a&gt; and &lt;a href="https://github.com/jupyter/notebook/releases/tag/v7.6.0"&gt;Notebook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Announcing jupyter-builder: A Standalone Build System for JupyterLab Extensions</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/" rel="alternate"/><published>2026-06-19T13:11:00+00:00</published><updated>2026-06-19T13:11:00+00:00</updated><author><name>Darshan Kr. Paudyal</name></author><id>tag:jasongrout.github.io,2026-06-19:/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/</id><summary type="html">&lt;p&gt;We’re excited to announce the first stable release of jupyter-builder on PyPI and @jupyter/builder on npm. A standalone, configurable build…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/announcing-jupyter-builder-a-standalone-build-system/images/001-1_3UBtK-sh01N6OXIbGFNrpQ.webp" alt="Jupyter Builder logo" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the first stable release of &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt; on PyPI and &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt; on npm. A standalone, configurable build system for JupyterLab and its extensions.&lt;/p&gt;
&lt;p&gt;If you build or maintain a JupyterLab extension, this release is for you.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;For a long time, building a JupyterLab extension has meant installing all of JupyterLab. Take a look at any extension’s build dependencies and you’ll see &lt;code&gt;jupyterlab&lt;/code&gt; listed there, not because the extension needs JupyterLab at runtime to be built, but because the build tooling lives &lt;em&gt;inside&lt;/em&gt; the JupyterLab repository. The &lt;code&gt;builder/&lt;/code&gt; folder, the &lt;code&gt;jlpm&lt;/code&gt; command, the helper scripts, they’re all coupled to the JupyterLab core.&lt;/p&gt;
&lt;p&gt;This setup has caused real friction over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linux packagers&lt;/strong&gt; have repeatedly raised concerns about the circular dependency where extensions like &lt;code&gt;jupyterlab_pygments&lt;/code&gt; need &lt;code&gt;jupyterlab&lt;/code&gt; to build, but &lt;code&gt;jupyterlab&lt;/code&gt; ships with those extensions. It’s a chicken-and-egg problem that makes packaging painful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension authors&lt;/strong&gt; have to update their build system every time they update to a new JupyterLab version, which sometimes introduces breakages that have nothing to do with their extension’s actual code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Users&lt;/strong&gt; still occasionally run into installation issues that trace back to the incomplete migration from “source” extensions to prebuilt ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The need to separate the build system was identified back in &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/13456"&gt;jupyterlab#13456&lt;/a&gt;, and initial work began during GSoC 2024. With the support of the Jupyter Foundation’s first community-funded proposal, we’ve now brought that work to a stable release.&lt;/p&gt;
&lt;h2 id="what-jupyter-builder-does"&gt;What &lt;code&gt;jupyter-builder&lt;/code&gt; does&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; extracts all the Node.js-based build tooling out of JupyterLab into a dedicated package. You install it on its own, and it builds your extension, no full JupyterLab installation required.&lt;/p&gt;
&lt;p&gt;It ships in two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;strong&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (PyPI) — the Python package that provides the &lt;code&gt;jupyter-builder&lt;/code&gt; CLI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;strong&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (npm) — the underlying JavaScript package containing the build scripts. The Python CLI invokes &lt;code&gt;build-labextension.js&lt;/code&gt; from this package, which then runs the &lt;a href="https://rspack.dev/"&gt;rspack&lt;/a&gt; compiler with all the configuration needed to produce a JupyterLab extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of &lt;code&gt;jupyter labextension build .&lt;/code&gt;, you now run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="build-compatibility-without-jupyterlab-installed"&gt;Build compatibility without JupyterLab installed&lt;/h2&gt;
&lt;p&gt;One interesting problem we had to solve: if JupyterLab isn’t installed, how does the builder verify that an extension is compatible with a given JupyterLab version?&lt;/p&gt;
&lt;p&gt;The answer is &lt;a href="https://www.npmjs.com/package/@jupyterlab/core-meta"&gt;&lt;code&gt;@jupyterlab/core-meta&lt;/code&gt;&lt;/a&gt;, a small npm package that publishes JupyterLab’s core metadata on its own. &lt;code&gt;@jupyter/builder&lt;/code&gt; declares a dependency on a specific &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; version, so by default your extension is checked for compatibility against whatever JupyterLab version that pin corresponds to. In general, the latest &lt;code&gt;@jupyter/builder&lt;/code&gt; will track the latest JupyterLab.&lt;/p&gt;
&lt;p&gt;If you want to build against a different JupyterLab version, pass &lt;code&gt;--core-version&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--core-version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.4.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll download that version’s metadata from &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; on npm and check compatibility against it.&lt;/p&gt;
&lt;h2 id="migrating-an-existing-extension"&gt;Migrating an existing extension&lt;/h2&gt;
&lt;p&gt;If you’re starting a new extension with the &lt;a href="https://github.com/jupyterlab/extension-template"&gt;extension template&lt;/a&gt;, you’ll get all of this set up automatically. For existing extensions, there are two changes to make.&lt;/p&gt;
&lt;h2 id="1-update-the-python-build-dependencies"&gt;1. Update the Python build dependencies&lt;/h2&gt;
&lt;p&gt;In your &lt;code&gt;pyproject.toml&lt;/code&gt;, swap &lt;code&gt;jupyterlab&lt;/code&gt; for &lt;code&gt;jupyter-builder&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyterlab&amp;gt;=4.0.0,&amp;lt;5&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyter-builder&amp;gt;=1.0.0&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then update the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; to use the new CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;build:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;build:labextension:dev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build --development True .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;watch:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder watch .&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your build no longer pulls in all of JupyterLab.&lt;/p&gt;
&lt;h2 id="2-update-the-npm-builder-dependency"&gt;2. Update the npm builder dependency&lt;/h2&gt;
&lt;p&gt;Replace &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;@jupyter/builder&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;@jupyter/builder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^1.0.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; is backwards-compatible with &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt;, so things will keep working if you don’t migrate immediately. That said, we strongly recommend switching: &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; is no longer being actively developed, and all new work is happening in &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that as part of this transition JupyterLab has also moved from Webpack to &lt;a href="https://rspack.rs/"&gt;Rspack&lt;/a&gt;. Most extensions won’t need any changes, but if yours uses a custom &lt;code&gt;webpackConfig&lt;/code&gt; see the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#building-extensions-with-rspack"&gt;Rspack migration notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;
&lt;p&gt;This release is the first stable foundation. We’re continuing to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrate more packages across the &lt;a href="https://github.com/jupyterlab/"&gt;Jupyter organization&lt;/a&gt; to use &lt;code&gt;jupyter-builder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make the builder more configurable, so it can eventually be used for projects beyond JupyterLab.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you maintain an extension, please try the migration and &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;let us know how it goes&lt;/a&gt;. Feedback from real extensions is the best way to make sure the migration path stays smooth.&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;This work was funded under the &lt;a href="/posts/2025/announcing-our-first-jupyter-community-funded-proposals/"&gt;first round of Jupyter Community Funded Proposals&lt;/a&gt;. We want to thank the &lt;a href="https://jupyterfoundation.org/"&gt;Jupyter Foundation&lt;/a&gt; and everyone involved in setting up this funding mechanism.&lt;/p&gt;
&lt;p&gt;A huge thank you to &lt;a href="https://github.com/bollwyvl"&gt;Nicholas Bollweg&lt;/a&gt; for many thoughtful discussions on the design and packaging of &lt;code&gt;jupyter-builder&lt;/code&gt;, and to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; for helping us resolve issues and for guiding the adoption of &lt;code&gt;jupyter-builder&lt;/code&gt; in Jupyter Notebook.&lt;/p&gt;
&lt;p&gt;This package was initially created during &lt;a href="https://summerofcode.withgoogle.com/archive/2024/projects/YMB3Rc53"&gt;GSoC 2024&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/ronan-coutinho/"&gt;Ronan Coutinho&lt;/a&gt;, mentored by &lt;a href="https://www.linkedin.com/in/fcollonval/"&gt;Frédéric Collonval&lt;/a&gt;. Their work laid the foundation that made this release possible.&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/darshan-poudel8/"&gt;&lt;strong&gt;Darshan Paudyal&lt;/strong&gt;&lt;/a&gt; is an intern at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt; and a member of the &lt;a href="https://jupyterlab-team-compass.readthedocs.io/en/latest/team.html"&gt;Jupyter Frontend Council&lt;/a&gt;. Darshan led the work to bring &lt;code&gt;jupyter-builder&lt;/code&gt; to a stable release, completing the separation from JupyterLab core and shepherding the package through to its first published version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and technical direction throughout the development of &lt;code&gt;jupyter-builder&lt;/code&gt;, helping shape its architecture and roadmap.&lt;/p&gt;
&lt;h2 id="useful-links"&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📦 npm: &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💻 Source: &lt;a href="https://github.com/jupyterlab/jupyter-builder"&gt;github.com/jupyterlab/jupyter-builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 Migration guide: &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#migrating-to-jupyter-builder"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; migration section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 Issues &amp;amp; feedback: &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>A User’s Journey with Plugin Playground: From First Idea to Installable JupyterLab Extension.</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/" rel="alternate"/><published>2026-05-28T17:45:00+00:00</published><updated>2026-05-28T18:07:00+00:00</updated><author><name>Anuj Singh</name></author><id>tag:jasongrout.github.io,2026-05-28:/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/</id><summary type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of Jupyter Notebook and JupyterLab plugins to add any functionality you may desire. To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install jupyterlab-plugin-playground
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or use it from Binder or JupyterLite without installing anything by clicking one of these links: &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=lab"&gt;Binder (Lab)&lt;/a&gt;, &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=tree"&gt;Binder (Notebook v7)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/lab/"&gt;JupyterLite (Lab)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/tree/"&gt;JupyterLite (Notebook v7)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="motivation"&gt;&lt;strong&gt;Motivation&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Building a JupyterLab extension has traditionally required setting up a complex local environment: installing dependencies from disjoint ecosystems (Node.js and Python), configuring tooling, and hunting down the right documentation. The goal of the Playground is to eliminate that setup overhead and streamline extension development. By bringing the runtime, documentation, and the context required for extension development into a single browser tab, you can go from idea to working extension without any local setup required.&lt;/p&gt;
&lt;h2 id="journey-at-a-glance"&gt;&lt;strong&gt;Journey At a Glance&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start with a tiny extension idea that is easy to verify visually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose a build path: &lt;strong&gt;Manual&lt;/strong&gt; or &lt;strong&gt;AI-assisted&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load&lt;/strong&gt; and iterate inside JupyterLab until behavior is stable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a second extension to prove the workflow is repeatable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Share plugin files/packages for product and engineering review.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;wheel(.whl)&lt;/strong&gt; and validate installation in a clean Binder runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;zip&lt;/strong&gt; and move the scaffold into a normal GitHub repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="10-pick-your-starting-path"&gt;&lt;strong&gt;1.0: Pick Your Starting Path&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;At the beginning, choose the path that matches your confidence level and speed needs. If this is your first time with Plugin Playground, you can optionally run &lt;code&gt;Take the Tour&lt;/code&gt; from the Launcher or Command Palette for quick orientation before starting.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Start from File&lt;/code&gt; If you already know the APIs you need and want direct control.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Build with AI&lt;/code&gt; If you know the intended behavior but want a first draft quickly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both paths converge to the same shipping workflow later.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/001-1_ejDftHO8TiktbPMim-EVqA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="20-build-extension-1-manually"&gt;&lt;strong&gt;2.0: Build Extension #1 Manually&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;For the first pass, we will create something small and observable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our Goal&lt;/strong&gt;: add a command named &lt;code&gt;Toggle Right Sidebar&lt;/code&gt; to the command palette.&lt;/p&gt;
&lt;h3 id="21-create-the-plugin-file"&gt;&lt;strong&gt;2.1: Create the plugin file&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Launch &lt;strong&gt;Start from File&lt;/strong&gt;. This comes with a dummy template.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="22-fill-the-missing-pieces-using-the-right-sidebar"&gt;&lt;strong&gt;2.2: Fill the missing pieces using the right sidebar&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now build the plugin in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;Tokens&lt;/code&gt;section in the right sidebar, search &lt;code&gt;ICommandPalette&lt;/code&gt; and click insert ( + icon ), so Plugin Playground adds token import and dependency wiring.&lt;/li&gt;
&lt;li&gt;Add a simple command skeleton inside the &lt;strong&gt;activate&lt;/strong&gt; function:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// place cursor here&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;In &lt;code&gt;Commands&lt;/code&gt;, search &lt;code&gt;application:toggle-right-area&lt;/code&gt;, place your cursor inside &lt;code&gt;execute&lt;/code&gt; of the added command ( see above ) and use &lt;code&gt;Insert in selection&lt;/code&gt; This adds at the cursor position:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Register the command in the palette:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After those edits, your file should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/apputils&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;requires&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Uses the typed plugin template (&lt;code&gt;JupyterFrontEndPlugin&amp;lt;void&amp;gt;&lt;/code&gt;) and typed &lt;code&gt;activate&lt;/code&gt; parameters. Imports &lt;code&gt;ICommandPalette&lt;/code&gt; so the plugin can add entries to the Command Palette.&lt;/li&gt;
&lt;li&gt;Registers a new command (&lt;code&gt;my-first-playground-plugin:toggle-sidebar&lt;/code&gt;) and executes &lt;code&gt;application:toggle-right-area&lt;/code&gt; when your command runs.&lt;/li&gt;
&lt;li&gt;Adds your command to the palette under the category &lt;code&gt;AAA&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="23-load-it-and-verify-behavior"&gt;&lt;strong&gt;2.3: Load it and verify behavior&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Click &lt;strong&gt;Load Current File As Extension or click the run button in the toolbar&lt;/strong&gt;, and open Command Palette.&lt;/p&gt;
&lt;p&gt;When you run &lt;strong&gt;Load Current File As Extension&lt;/strong&gt;, Plugin Playground compiles the active file and registers the plugin object into the live JupyterLab session. During iteration, if a plugin with the same &lt;strong&gt;id&lt;/strong&gt; already exists, Plugin Playground attempts to deactivate and replace it so reload loops stay fast.&lt;/p&gt;
&lt;h3 id="expected-result"&gt;&lt;strong&gt;Expected result&lt;/strong&gt;:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Toggle Right Sidebar&lt;/strong&gt; appears in the command palette ( open command palette by going through &lt;strong&gt;view -&amp;gt; Activate command palette&lt;/strong&gt; or Press &lt;strong&gt;Accel&lt;/strong&gt; + &lt;strong&gt;shift&lt;/strong&gt; + &lt;strong&gt;C&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/002-1_RXMnfrUXeu1q7AkukZEF_A.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Running it hides/shows the JupyterLab right sidebar.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="30-build-with-ai"&gt;&lt;strong&gt;3.0: Build with AI&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you want a faster draft, use AI assistance for the same extension workflow.&lt;/p&gt;
&lt;p&gt;Goal: add the &lt;strong&gt;Show Active Notebook Cell Count&lt;/strong&gt; command with a friendly no-notebook fallback.&lt;/p&gt;
&lt;h3 id="31-give-ai-a-precise-request"&gt;&lt;strong&gt;3.1: Give AI a precise request&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Use &lt;strong&gt;Build with AI&lt;/strong&gt; and give a prompt:&lt;/p&gt;
&lt;p&gt;for example, something like:-&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Create a JupyterLab plugin named &amp;quot;active-notebook-cell-counter&amp;quot;.
Add a command &amp;quot;Show Active Notebook Cell Count&amp;quot;.
If no notebook is active, show a friendly dialog message.
If a notebook is active, show the current cell count in a dialog.
Register the command in the command palette under category &amp;quot;Playground Demo&amp;quot;.
Export default plugin object.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;This gives AI a concrete plugin goal, command name, and fallback behavior requirements.&lt;/p&gt;
&lt;h3 id="32-use-ai-deterministic-inserts-together"&gt;&lt;strong&gt;3.2: Use AI + deterministic inserts together&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Commands&lt;/strong&gt; tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable boilerplate insertion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prompt AI to insert&lt;/strong&gt; when placement context is tricky.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;{n}&lt;/strong&gt; when you need argument shape clarity ( Inspect the command signature before insertion: it shows expected argument names/types and return details).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use &lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable snippets, and switch to AI when insertion location or code adaptation depends on the surrounding context.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/003-1_HAs9F-B9l77KolI3ZZTH3g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h3 id="40-iterate-faster-before-handoff"&gt;&lt;strong&gt;4.0: Iterate Faster Before Handoff&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Once the extensions run, enable &lt;strong&gt;Run on save&lt;/strong&gt;(from the toolbar or from the settings) while polishing labels, command categories, and small behaviour details.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;Run on save&lt;/strong&gt;, each file save triggers a reload loop for that plugin file, which makes label tweaks, command grouping changes, and message-copy iteration much faster than manual reload cycles. And with enabling the global setting (&lt;strong&gt;Load as extension on save&lt;/strong&gt;), this behaviour is present for all files by default.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/004-1_wfxS4HhzRfU5-bbKYfPonA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h3 id="50-share-for-review"&gt;&lt;strong&gt;5.0: Share for Review&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Before packaging, you can ask a teammate to review behaviour or just share links for the extension for a quick look using:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a single file&lt;/strong&gt; when sharing is about one plugin file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a package&lt;/strong&gt; when your logic spans several files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This keeps review lightweight: collaborators can inspect and discuss behaviour before you commit to repository structure, CI, and release wiring.&lt;/p&gt;
&lt;h3 id="60-export-as-wheel-and-validate-in-binder"&gt;&lt;strong&gt;6.0: Export as Wheel and Validate in Binder&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now test as an installable artifact in a separate environment.&lt;/p&gt;
&lt;h3 id="61-export-whl"&gt;&lt;strong&gt;6.1: Export &lt;code&gt;.whl&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;From the export dropdown ( in toolbar ), choose &lt;strong&gt;Export as Python package (.whl)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="62-install-in-binder"&gt;&lt;strong&gt;6.2: Install in Binder&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open JupyterLab on Binder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload the downloaded wheel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a terminal and run:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;force-reinstall&lt;span class="w"&gt; &lt;/span&gt;./your_exported_package.whl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does-1"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;Installs the wheel you just exported from Plugin Playground into Binder. (Uses &lt;strong&gt;— force-reinstall&lt;/strong&gt; so Binder replaces any previously installed copy with your newest build). Then refresh JupyterLab and verify commands.&lt;/p&gt;
&lt;h3 id="70-export-as-zip-and-create-a-github-repository"&gt;&lt;strong&gt;7.0: Export as Zip and Create a GitHub Repository&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;After runtime validation, move to normal engineering workflows.&lt;/p&gt;
&lt;h3 id="71-export-zip"&gt;&lt;strong&gt;7.1: Export &lt;code&gt;.zip&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Choose &lt;code&gt;**Export as archive (.zip)**&lt;/code&gt; from the toolbar export dropdown.&lt;/p&gt;
&lt;h3 id="72-bootstrap-repository"&gt;&lt;strong&gt;7.2: Bootstrap repository&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Unzip locally, create an empty GitHub repo, then run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Initial extension scaffold from Plugin Playground&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//github.com/&amp;lt;your-org-or-user&amp;gt;/my-jlab-extension.git&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-this-block-does-2"&gt;What this block does:&lt;/h3&gt;
&lt;p&gt;Initializes a new Git repository from the exported scaffold and creates an initial commit that captures the generated extension baseline.&lt;/p&gt;
&lt;p&gt;Now your prototype is in a standard repo flow with PRs, CI, and release automation.&lt;/p&gt;
&lt;h3 id="whats-next"&gt;What’s Next&lt;/h3&gt;
&lt;p&gt;Plugin Playground is still evolving. We’re actively working on the &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/225"&gt;LSP integration&lt;/a&gt; for JupyterLite to bring code intelligence autocomplete and diagnostics directly into the editor, an &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/231"&gt;“Ask AI” button on log errors&lt;/a&gt; that lets you send error context directly into the AI chat for instant debugging help. In the future, we would like to explore &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/139"&gt;git integration&lt;/a&gt; to snapshots every successful load, paired with a &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/140"&gt;built-in diff viewer&lt;/a&gt; to compare any snapshot against your current file. If you have any suggestions or encounter any problems, please let us know by opening &lt;a href="https://github.com/jupyterlab/plugin-playground/issues"&gt;an issue on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="acknowledgements"&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We are grateful to the &lt;a href="https://jupyterfoundation.org/join/"&gt;Jupyter Foundation&lt;/a&gt; and its &lt;a href="https://jupyterfoundation.org/members/"&gt;members&lt;/a&gt; for sponsoring the development of Plugin Playground as part of &lt;a href="https://jupyterfoundation.org/community-funding-proposals/"&gt;Community Funding Proposals 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We thank Jeremy Tuloup, Nick Bollweg, and Nicolas Brichet for providing inspiration, review, and advice across our work on the plugin and its dependencies. Finally, this work would not be possible without the &lt;a href="https://github.com/jupyterlab/plugin-playground/graphs/contributors?from=01%2F01%2F2020&amp;amp;to=18%2F12%2F2024"&gt;authors of earlier&lt;/a&gt; iterations of Plugin Playground.&lt;/p&gt;
&lt;h3 id="about-the-developers"&gt;&lt;strong&gt;About the&lt;/strong&gt; Developers&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/anuj-singh-2b2ab6206/"&gt;&lt;strong&gt;Anuj Singh&lt;/strong&gt;&lt;/a&gt; is an OSS intern at OpenTeams. Anuj implemented Plugin Playground v1.0.0 and contributed to its dependencies during his internship at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/smera-goel/"&gt;&lt;strong&gt;Smera Goel&lt;/strong&gt;&lt;/a&gt; is a UI/UX designer at &lt;a href="https://quansight.com/"&gt;Quansight&lt;/a&gt;. Smera contributed to the user experience and design direction of Plugin Playground.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and direction for the development of the Plugin Playground.&lt;/p&gt;
&lt;h3 id="further-reading"&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Blog post on &lt;a href="https://openteams.com/plugin-playground-ai-integration/"&gt;how and why we integrated AI functions in the playground&lt;/a&gt; extension&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/CHANGELOG.md"&gt;Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/README.md"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/><category term="JupyterLite"/></entry><entry><title>700 JupyterLab 4 Extensions!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/" rel="alternate"/><published>2026-03-13T17:18:00+00:00</published><updated>2026-03-13T17:18:00+00:00</updated><author><name>Konstantin Taletskiy</name></author><id>tag:jasongrout.github.io,2026-03-13:/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/</id><summary type="html">&lt;p&gt;The JupyterLab extension ecosystem just crossed 700 extensions compatible with JupyterLab 4!&lt;/p&gt;
</summary><content type="html">&lt;figure&gt;
&lt;img alt="The number 700 formed by a mosaic of JupyterLab extension icons and author avatars" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/001-1_z7SyUie14-caE28XRvtH9A.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;700 extensions for JupyterLab 4, and counting!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The JupyterLab extension ecosystem just crossed &lt;strong&gt;700 extensions compatible with JupyterLab 4!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s 700 community-built plugins — from astronomical data viewers to reactive notebooks, from genome browsers to workflow managers — created by hundreds of developers, research labs, and companies around the world.&lt;/p&gt;
&lt;h2 id="what-are-jupyterlab-extensions"&gt;What Are JupyterLab Extensions?&lt;/h2&gt;
&lt;p&gt;Extensions are how JupyterLab becomes a Git client, a dashboard builder, a genomics viewer, or an AI workspace — without changing the core application. Install one with &lt;code&gt;pip install&lt;/code&gt;, and it activates automatically.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Collage of screenshots showing popular JupyterLab extensions in action: jupytext with file format options, jupyterlab-h5web visualizing HDF5 data, jupyter-collaboration for real-time editing, jupyterlab-git with diff and staging views, ipywidgets with interactive parameter sliders, ipympl for inline matplotlib plots, jupytergis-lab for geospatial data, sidecar for side-panel output, and jupyterlab-latex for document preview." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/002-1_TYJMNY9zzWcawsQsOkPuLQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Popular JupyterLab extensions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is by design: JupyterLab itself is built as a collection of extensions — the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/filebrowser"&gt;file browser&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/notebook"&gt;notebook editor&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/terminal"&gt;terminal&lt;/a&gt; are all plugins. The same architecture that powers the core lets the community build what they need. For background, see &lt;a href="/posts/2019/99-ways-to-extend-the-jupyter-ecosystem/"&gt;99 ways to extend the Jupyter ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-ecosystem-at-700"&gt;The Ecosystem at 700&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;700+ extensions compatible with JupyterLab 4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~960 total extensions&lt;/strong&gt; published on PyPI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~9.8 million downloads/month&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100M+ total downloads&lt;/strong&gt; in the past year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By any measure, a substantial software layer has grown around JupyterLab.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How We Got Here&lt;/h2&gt;
&lt;p&gt;The ecosystem crossed &lt;strong&gt;600 JL4-compatible extensions in late October 2025&lt;/strong&gt;, days before &lt;a href="https://www.jupytercon.com/"&gt;JupyterCon in San Diego&lt;/a&gt;. At the conference, we ran a full-day &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;Extension Development for Everyone&lt;/a&gt; tutorial with hands-on rapid prototyping. By early March 2026, we hit &lt;strong&gt;700&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The ecosystem has been growing at a steady pace, averaging about 18 new extensions per month, with November 2025 setting an all-time monthly record of 33. Modern tooling is helping: better templates, documentation, and code generation tools have lowered the bar for what once required deep familiarity with TypeScript, Lumino, and JupyterLab internals.&lt;/p&gt;
&lt;h2 id="where-the-extensions-are"&gt;Where the Extensions Are&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing number of JupyterLab extensions by category. Development and Version Control leads with 267, followed by Cloud and Platform Integration (127), AI and Code Assistance (88), Specialized Computing (83), Visualization and Dashboards (80), Educational and Grading (70), Theme (67), System and Resource Management (65), Runtime and Kernel Extensions (44), Workflow and Automation (37), and Data and Database Integration (26)" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/003-1_lijkfmv6G5n96sO2vRzB5Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Number of JupyterLab extensions by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing 30-day PyPI downloads per category. Development and Version Control leads at 5.4 million, followed by Visualization and Dashboards (2.7M), System and Resource Management (602K), AI and Code Assistance (253K), Runtime and Kernel Extensions (206K), Cloud and Platform Integration (178K), Data and Database Integration (148K), Educational and Grading (99K), Workflow and Automation (69K), Specialized Computing (56K), and Theme (48K)" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/004-1_wW76DvGMkq2l_Xvr0JO_vw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Monthly PyPI downloads by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Development &amp;amp; Version Control dominates both in count (267) and downloads (5.4M/month). Visualization &amp;amp; Dashboards (2.7M/month) and System &amp;amp; Resource Management (602K/month) round out the top three most downloaded categories. But the fastest-growing categories point to where things are heading. Here’s what’s new in 2026:&lt;/p&gt;
&lt;h2 id="jupyterlabs-ai-layer-starts-taking-shape"&gt;JupyterLab’s AI Layer Starts Taking Shape&lt;/h2&gt;
&lt;p&gt;AI isn’t yet the biggest category in JupyterLab, but it may be the clearest signal of where new interaction patterns are emerging:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-ai-acp-client?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-ai-acp-client&lt;/strong&gt;&lt;/a&gt; — Brings external AI agents into JupyterLab’s chat via the Agent Communication Protocol. Ships with Claude Code and Kiro personas.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/nb-margin?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;nb-margin&lt;/strong&gt;&lt;/a&gt; — Annotate cells with margin comments, and Claude Code edits them. A different paradigm from chat-based AI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlite-ai-kernels?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlite-ai-kernels&lt;/strong&gt;&lt;/a&gt; — AI-powered kernels for JupyterLite, from Jeremy Tuloup. AI-assisted computation entirely in the browser.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-chat-components?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-chat-components&lt;/strong&gt;&lt;/a&gt; — Reusable chat UI components from Project Jupyter — building blocks for the next generation of AI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These extensions reflect what the JupyterLab team identified as a 2026 priority: first-class integration with AI tooling.&lt;/p&gt;
&lt;h2 id="reproducibility-gets-a-toolchain"&gt;Reproducibility Gets a Toolchain&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/calkit-python?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;calkit-python&lt;/strong&gt;&lt;/a&gt; is the most downloaded new extension of 2026 (11,000+ monthly downloads). It gives notebooks project-scoped environments, graphical package management via Astral’s &lt;code&gt;uv&lt;/code&gt;, and one-click notebook pipelines with freshness tracking. Think “Makefiles for notebooks” meets “Poetry for Jupyter.”&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Calkit extension in JupyterLab showing a notebook pipeline with three stages — collect-data, process-data, and plot-results — in the left sidebar, alongside a Python notebook with pandas code. The toolbar shows environment and pipeline stage indicators" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/005-0_vKMU4QiLD406Ac9c.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Calkit manages notebook pipelines with environment tracking and one-click reruns. The orange ‘run’ button signals stale outputs that need to be regenerated.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-projspec?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyter-projspec&lt;/strong&gt;&lt;/a&gt; (from the fsspec contributors) takes a complementary approach — it brings &lt;a href="https://github.com/fsspec/projspec"&gt;projspec&lt;/a&gt; into JupyterLab, letting you scan and analyze project structures directly from the notebook environment.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jupyter-projspec extension in JupyterLab showing the Project Spec sidebar panel with detected project types including Git Repository, Pixi, and Poetry, alongside the file browser and JupyterLab launcher with notebook and console options." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/006-0_GSE92ExuZWmjQHtA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jupyter-projspec integrates to system filebrowser to show the project metadata&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="science"&gt;Science&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/fitsview?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;fitsview&lt;/strong&gt;&lt;/a&gt; — Stream FITS astronomical data slices directly in JupyterLab without downloading full files.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-urdf-test?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-urdf-test&lt;/strong&gt;&lt;/a&gt; — 3D robot model viewer/editor (URDF + Three.js), from &lt;a href="https://github.com/jupyter-robotics"&gt;jupyter-robotics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/climb-jupyter-igv?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;climb-jupyter-igv&lt;/strong&gt;&lt;/a&gt;— Integrative Genomics Viewer with S3 access for bioinformatics.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/ggblab?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;ggblab&lt;/strong&gt;&lt;/a&gt;— GeoGebra interactive geometry with bidirectional Python communication. Second most downloaded new extension of 2026.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="accessibility"&gt;Accessibility&lt;/h2&gt;
&lt;p&gt;Accessibility has been a growing focus for JupyterLab core and extensions are starting to address it too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-a11y-checker?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-a11y-checker&lt;/strong&gt;&lt;/a&gt;— From UC Berkeley’s &lt;a href="https://github.com/berkeley-dsep-infra/jupyterlab-a11y-checker"&gt;DSEP infrastructure team&lt;/a&gt;, this extension scans notebooks for WCAG 2.1 AA issues: missing alt text, heading structure, table headers, color contrast, and link text. Guided fix interfaces, optional AI suggestions, and a CLI for CI pipelines. Over 11,000 total downloads and a &lt;a href="https://a11y-checker-guide.datahub.berkeley.edu/"&gt;documentation site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-change-ui-font-size-fix?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;&lt;strong&gt;jupyterlab-change-ui-font-size-fix&lt;/strong&gt;&lt;/a&gt; — Fixes file browser icon misalignment when users change the UI font size — a small but real pain point for anyone who needs larger text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="27-extensions-one-platform"&gt;27 Extensions, One Platform&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/stellarshenson/stellars-jupyterlab-ds"&gt;Stellars&lt;/a&gt; is a JupyterLab-based data science platform — GPU support, MLflow, TensorBoard, Optuna — assembled from &lt;strong&gt;27 custom extensions&lt;/strong&gt; covering everything from &lt;a href="https://labextensions.dev/extensions/jupyterlab-branding-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;branding&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;file icons&lt;/a&gt; to &lt;a href="https://labextensions.dev/extensions/jupyterlab-kernel-terminal-workspace-culler-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;kernel management&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-drawio-render-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;diagram rendering&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;VS Code file icons&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-trash-mgmt-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;trash management&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-mmd-to-png-extension?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=700_extensions"&gt;Mermaid-to-PNG conversion&lt;/a&gt;, and more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JupyterLab is now flexible enough that one developer can assemble a domain-specific product entirely from extension building blocks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="want-to-build-your-own"&gt;Want to Build Your Own?&lt;/h2&gt;
&lt;p&gt;The JupyterCon tutorial is fully available: &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;step-by-step materials&lt;/a&gt; and the complete &lt;a href="https://www.youtube.com/watch?v=z-KZ6CjZjbM"&gt;YouTube recording&lt;/a&gt;. It covers scaffolding, plugin architecture, publishing to PyPI, and rapid prototyping techniques. The tools have never been more accessible.&lt;/p&gt;
&lt;h2 id="how-we-track-this"&gt;How We Track This&lt;/h2&gt;
&lt;p&gt;The data behind this post comes from the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;, a community &lt;a href="https://github.com/orbrx/jupyter-marketplace"&gt;project&lt;/a&gt; that tracks all published JupyterLab extensions using PyPI data. The marketplace refreshes automatically and provides download trends, category breakdowns, and discovery tools.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Marketplace homepage showing a grid of popular extensions with download counts and GitHub stars — including jupyter-archive, jupyter-resource-usage, ipyanchorviz, jupyterlab-execute, jupyter-collaboration, jupysql-plugin, jupyterlab-unfold, jupyter-ai, jupyterlab-code-snippets, and jupyterlab-autoscroll." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/700-jupyterlab-4-extensions/images/007-1_CkZpwkmEKNiXm_XsiBRACQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Marketplace&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For more on the data and methodology, see our &lt;a href="https://www.youtube.com/watch?v=OWt3Yzhrs1E"&gt;PyData Boston 2025 talk&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New interaction patterns&lt;/strong&gt; are still being figured out — chat-based assistance, cell annotations, agent protocols. Probably all of them for different use cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility tooling&lt;/strong&gt; suggests the community is ready for opinionated workflow management built into the notebook experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ensuring extensions keep working as JupyterLab evolves is critical — the team has been &lt;a href="https://github.com/jupyterlab/frontends-team-compass/issues/301"&gt;discussing extension compatibility testing&lt;/a&gt; at recent contributors calls.&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://labextensions.dev"&gt;Marketplace&lt;/a&gt; itself, we’re working on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deeper integration with JupyterLab Extension Manager&lt;/strong&gt; — deep links and “Install in JupyterLab” buttons to go from discovery to installation in one click.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expanding Trove classifiers&lt;/strong&gt; to indicate Jupyter Notebook and JupyterLite support. All three use the same extension system, with important caveats: Notebook extensions need to target different UI elements, and JupyterLite extensions cannot have a server component.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better contribution signals&lt;/strong&gt; — surfacing commits, PRs, and issues to help users gauge how actively maintained an extension is.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;At 700 extensions, the community now shapes JupyterLab as much as the core team does. If you’re building extensions, thank you! Every one of them makes Jupyter better for someone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="about-the-author"&gt;About the author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/taletskiy/"&gt;Konstantin Taletskiy&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt; working on open-source Jupyter. He is a contributor to JupyterLab, maintainer of &lt;a href="https://github.com/mamba-org/mamba-gator"&gt;mamba-gator&lt;/a&gt; and &lt;a href="https://github.com/jupyterlab/jupyterlab-latex"&gt;jupyterlab-latex&lt;/a&gt;, and the creator of the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;.&lt;/p&gt;
</content><category term="community"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Instantly view Parquet files in JupyterLab with Arbalister</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/" rel="alternate"/><published>2026-01-29T16:18:00+00:00</published><updated>2026-02-04T14:50:00+00:00</updated><author><name>Antoine Prouvost</name></author><id>tag:jasongrout.github.io,2026-01-29:/medium-archive/pelican/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/</id><summary type="html">&lt;p&gt;A colleague sends you a SQLite file, a Parquet dataset, or an Avro snapshot. You need to explore the content, but you’re not sure what’s…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A colleague sends you a SQLite file, a Parquet dataset, or an Avro snapshot.&lt;br&gt;
You need to explore the content, but you’re not sure what’s inside or even how to open it. You shouldn’t have to write code, craft SQL queries, or recall the syntax for &lt;code&gt;read_parquet&lt;/code&gt;in a Python library. Instead, you should be able to click the file once and instantly see the tables and metadata.&lt;/p&gt;
&lt;p&gt;Despite being the &lt;em&gt;de-facto&lt;/em&gt; standard for data science and exploration, JupyterLab lacks this basic capability out of the box. Of course, there are extremely well designed libraries to read, process, and transform tabular data — but opening and viewing a file with a table should be simpler. A workflow with libraries like Pandas or Polars require you to install relevant packages, create a notebook, and iterate on queries to get a full picture of the data.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.max_rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.max_columns&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.width&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/2025/PERFORMANCE_SNAPSHOT_2025_01_05.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Whether for a newcomer to data science, or someone that needs to investigate many different files with hard to remember schemas, this is a bit cumbersome. Comma separated value files (CSV) have better support: double click on the file to see it open (see figure below). That is a great way to understand the business logic and answer questions such as &lt;em&gt;What type of information is in this file? What do values typically look like? Does it seem to contain all the data I need? What is the difference between &lt;code&gt;customer_id&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The built-in CSV viewer of JupterLab shows data as a table." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/001-1_pHSj3jmo0D69yzM86_XAZg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The JupyterLab CSV viewer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Arbalister&lt;/strong&gt;, our new JupyterLab extension, changes this. With Arbalister, you can &lt;strong&gt;double-click to instantly view&lt;/strong&gt; a wide range of tabular data files: &lt;strong&gt;Parquet, CSV, Avro, ORC, SQLite,&lt;/strong&gt; and more…&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Arbalister viewer can open Parquet files and more" src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/002-1_CHOorqcOpRmlHlHFIPpF-A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Arbalister viewer can open Parquet files and more&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The extension will load data as it is displayed, making it possible to view it almost instantaneously even if the file does not fit in memory. A toolbar lets the user select some reading options, such as the delimiter for a CSV file, or selecting the table to display in the SQLite file!&lt;/p&gt;
&lt;h2 id="arrow-over-the-wire"&gt;Arrow over the wire&lt;/h2&gt;
&lt;p&gt;Fueling it all is the &lt;a href="https://arrow.apache.org/"&gt;Apache Arrow&lt;/a&gt; ecosystem, an in-memory format for tabular data used in most data science libraries. Plenty of files types can already be read into an Arrow data structure so we exploit it heavily in Arbalister (with &lt;a href="https://datafusion.apache.org/"&gt;Apache Datafusion&lt;/a&gt; in our case).&lt;/p&gt;
&lt;p&gt;The table displayed in the user’s browser reuses JupyterLab Lumino DataGrid with a custom data model. In the latter, we divide the whole table in chunks across both the rows and columns axes (&lt;em&gt;e.g.&lt;/em&gt; 512 rows and 24 columns). When we need to display data from a chunk that is not already available, we make an HTTP request to a server-side extension that open the file, read the relevant portion into an Arrow table and return it to the client. The table is returned as Arrow IPC (Inter Process Communication, a binary format close to the original Arrow memory) in the response body. In the client, a lightweight Arrow implementation can read it back efficiently.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="A diagram show a client server-server architecture for Arbalister, with the backend responsible for reading many different file types into an Arrow IPC format, while the frontend displays it." src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/003-1_6qFKCpSW5d3GT1p6EOfekg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Arbalister client-server architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When nothing else is happening, the implementation will also pre-fetch some of the next chunks in the background so that scrolling past a chunk boundary is imperceptible.&lt;/p&gt;
&lt;p&gt;Because it all works in Arrow, which is standard for tabular data, adding support for new types of files that can be read into the Arrow format is extremely easy. Vortex, Lance, or whichever other format becomes relevant. But that is not all…&lt;/p&gt;
&lt;p&gt;The existing SQLite support is made possible through &lt;a href="https://arrow.apache.org/adbc/"&gt;ADBC&lt;/a&gt;, an SQL connector that reads databases into Arrow format. SQLite databases fit into single files, which fit the abstraction used in Arbalister, but ADBC could also be used to add general database exploration (Postgres &lt;em&gt;etc&lt;/em&gt;.) into Arbalister. With Datafusion, we could also add visualization for data lakehouses: partitioned datasets (&lt;em&gt;e.g.&lt;/em&gt; &lt;a href="https://delta.io/"&gt;DeltaLake&lt;/a&gt; or &lt;a href="https://iceberg.apache.org/"&gt;Apache Iceberg&lt;/a&gt;) over remote object storage (S3). With Intelligent predicate and projections pushdowns, we could visualize remote tera-byte table instantly by only downloading the relevant parts.&lt;/p&gt;
&lt;h2 id="looking-further"&gt;Looking further&lt;/h2&gt;
&lt;p&gt;There are multiple things that would make great improvements to Arbalister. We have already mentioned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optional support for newer file types: Vortex, Lance;&lt;/li&gt;
&lt;li&gt;Object storage and data lakehouses support;&lt;/li&gt;
&lt;li&gt;SQL databases support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another direction is support for &lt;a href="https://jupyterlite.readthedocs.io/en/stable/"&gt;JupyterLite&lt;/a&gt;, a &lt;a href="https://webassembly.org/"&gt;WebAssembly&lt;/a&gt; distribution of JupyterLab running entirely in the browser. This setting is great for education because it does not require more than a static file server to host.&lt;br&gt;
Being able to work directly with Parquet files in the browser is great for making dashboard and interactive demonstrations. Arrow and Datafusion already have some experimental support for WebAssembly so this is not so far-fetched.&lt;/p&gt;
&lt;p&gt;Finally Arbalister could use a few features to investigate deeper questions about the data. For instance, beyond the &lt;em&gt;What does the data look like?&lt;/em&gt; question, some column filters could help answer questions &lt;em&gt;such as What does the data look like in November 2024 for John Doe?&lt;/em&gt; Perhaps even an SQL console for more advanced queries.&lt;/p&gt;
&lt;h2 id="an-extension-point"&gt;An extension point&lt;/h2&gt;
&lt;p&gt;The server extension being only a way to serve Arrow data, it can be reused by other libraries to use it in other ways. For instance, if we knew that our table contains time-series data, a second viewer could display it as a graph.&lt;br&gt;
Similarly for geospatial data.&lt;/p&gt;
&lt;p&gt;If you are interested to use or contribute to Arbalister, head to &lt;a href="https://github.com/QuantStack/Arbalister"&gt;our GitHub&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;Antoine Prouvost is a senior scientific software engineer at QuantStack. His work on Arbalister was funded by Bloomberg.&lt;/p&gt;
</content><category term="JupyterLab"/><category term="visualization"/></entry><entry><title>JupyterLab 4.5 and Notebook 7.5 are available!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/" rel="alternate"/><published>2025-11-24T09:32:00+00:00</published><updated>2025-11-24T09:32:00+00:00</updated><author><name>Jeremy Tuloup</name></author><id>tag:jasongrout.github.io,2025-11-24:/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/</id><summary type="html">&lt;p&gt;JupyterLab 4.5 has been released! This new minor release of JupyterLab includes 51 new features and enhancements, 81 bug fixes, 44…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; 4.5 has been released! This new minor release of JupyterLab includes 51 new features and enhancements, 81 bug fixes, 44 maintenance tasks and 38 documentation improvements. There were 51 contributors to this release.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter/notebook"&gt;Jupyter Notebook&lt;/a&gt; 7.5 has also been released, including many of the fixes and enhancements in this JupyterLab 4.5 release. While the releases on GitHub provide additional detail on the updates made to both &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.5.0"&gt;JupyterLab 4.5&lt;/a&gt; and &lt;a href="https://github.com/jupyter/notebook/releases/tag/v7.5.0"&gt;Notebook 7.5&lt;/a&gt;, this article highlights some particularly notable changes.&lt;/p&gt;
&lt;p&gt;Extension authors can refer to the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#jupyterlab-4-4-to-4-5"&gt;Extension Migration Guide&lt;/a&gt; for information on API updates and details regarding changes to core packages.&lt;/p&gt;
&lt;h2 id="performance-and-windowing"&gt;Performance and windowing&lt;/h2&gt;
&lt;p&gt;The default windowing mode is now &lt;code&gt;contentVisibility&lt;/code&gt; which improves cell rendering and alleviates previously reported issues. The minimap can be enabled for all windowing modes and all notebooks via the &lt;code&gt;Settings&lt;/code&gt; menu, or by toggling the minimap per notebook with the &lt;code&gt;View&lt;/code&gt; -&amp;gt; &lt;code&gt;Show Minimap&lt;/code&gt; option. Additional fixes improve layout containment and scrollbar behavior when working with long cells.&lt;/p&gt;
&lt;h2 id="notebook-improvements"&gt;Notebook improvements&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Saving large notebooks is now faster&lt;/strong&gt; and more responsive thanks to streamed uploads and users can now open notebooks without starting a kernel, via the &lt;code&gt;Open With&lt;/code&gt; context menu option.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Open a notebook without a kernel" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/images/001-0_zjjwutgF-mDuypaA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Open a notebook without a kernel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Markdown support has been expanded&lt;/strong&gt; for using grid and flex based layouts and the Table of Contents has been significantly improved to better handle markdown headings, fixing issues with HTML tags, comments, and horizontal lines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Copying and pasting cells across browser tabs is now supported&lt;/strong&gt; and the “Line Wrap” toggle in settings now correctly applies line wrapping to cells.&lt;/p&gt;
&lt;p&gt;Other improvements include: restored scrolling to in-notebook links, manual saving support in collaborative mode, and improved performance and memory usage when streaming text outputs.&lt;/p&gt;
&lt;h2 id="debugger-and-console-improvements"&gt;Debugger and Console improvements&lt;/h2&gt;
&lt;p&gt;The debugger’s evaluate dialog has been replaced with an interactive console with syntax highlighting and code completion, and the variables panel now preserves its contents when switching editors. Selected breakpoints are now visually highlighted and breakpoints correctly handle empty lines and duplicate clicks. A visual indicator is displayed when the debugger is paused.&lt;/p&gt;
&lt;p&gt;Debugger panels as well as console debugging panels now reference the cell execution count helping users relate breakpoints, call stacks and source code to the cells they originate from.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab 4.5 includes many improvements to the debugger" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/images/002-0_5p78_XdDJh5oSnci.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab 4.5 includes many improvements to the debugger&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For the Log Console, users can now configure the default log level via settings, as well as customise the toolbar.&lt;/p&gt;
&lt;h2 id="new-search-functionality-inside-terminals"&gt;New search functionality inside terminals&lt;/h2&gt;
&lt;p&gt;The terminal now includes search functionality, with search highlights that adapt to theme changes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Search is now compatible with terminals" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/images/003-0_cJt44woU3gYoMhHu.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Search is now compatible with terminals&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="media-and-content-support"&gt;Media and content support&lt;/h2&gt;
&lt;p&gt;JupyterLab now includes built-in audio and video viewers, allowing users to open audio and video files directly from within JupyterLab.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Open audio and video files in JupyterLab" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/images/004-0_Jd_3FHeg3saSiwRs.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Open audio and video files in JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="file-browser-enhancements"&gt;File browser enhancements&lt;/h2&gt;
&lt;p&gt;Uploaded files can auto-open, with controls for size limits and behavior, and a new &lt;code&gt;allowFileUploads&lt;/code&gt; setting provides control over upload permissions.&lt;/p&gt;
&lt;p&gt;There is now a menu option for creating new Python, R, or Julia files, and a “Select All” command has also been added, also accessible via the &lt;code&gt;Ctrl + A&lt;/code&gt; keyboard shortcut.&lt;/p&gt;
&lt;p&gt;Users can now configure file browser breadcrumbs through the settings editor options &lt;code&gt;breadcrumbsLeftItems&lt;/code&gt; and &lt;code&gt;breadcrumbsRightItems&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab 4.5 includes many improvements to the file browser" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-5-and-notebook-7-5-are-available/images/005-0_V05aZinAy9fytWaU.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab 4.5 includes many improvements to the file browser&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Updates to file dialogs and navigation have addressed column spacing, preferred-directory handling and double-click behavior.&lt;/p&gt;
&lt;h2 id="keyboard-shortcuts-and-commands"&gt;Keyboard shortcuts and commands&lt;/h2&gt;
&lt;p&gt;Code fold and unfold commands have been added and shortcuts for these can be set in the settings editor.&lt;/p&gt;
&lt;p&gt;The “Show Workspace Indicator” command has been added to the command palette and the “Send Code to Console” shortcut has been restored. The previous issue involving editing shortcuts on Safari loosing focus, has been resolved.&lt;/p&gt;
&lt;p&gt;With Jupyter Notebook 7.5.0, the &lt;code&gt;O&lt;/code&gt; key has been defined as the keyboard shortcut to toggle cell outputs, restoring this familiar functionality from the classic notebook.&lt;/p&gt;
&lt;h2 id="settings"&gt;Settings&lt;/h2&gt;
&lt;p&gt;The settings editor now supports a &lt;code&gt;toSkip&lt;/code&gt; configuration to hide specific plugins from view.&lt;/p&gt;
&lt;p&gt;A new setting, &lt;code&gt;addExtraLineOnCellMerge&lt;/code&gt;, allows to remove the extra line added when merging cells, which is expected behavior based on the classic notebook.&lt;/p&gt;
&lt;h2 id="user-interface-and-accessibility"&gt;User interface and accessibility&lt;/h2&gt;
&lt;p&gt;JupyterLab can now automatically switch between light and dark themes when “Synchronize with System Settings” is enabled.&lt;/p&gt;
&lt;p&gt;Dialog components display buttons and checkboxes on separate lines for better readability, and selections in the terminal under high-contrast themes have better visibility.&lt;/p&gt;
&lt;p&gt;Keyboard navigation and tab order in the status bar has also been improved.&lt;/p&gt;
&lt;h2 id="update-from-jupytercon"&gt;Update from JupyterCon&lt;/h2&gt;
&lt;p&gt;At JupyterCon 2025 in San Diego, the Jupyter Frontends team presented an update on features released since JupyterLab 4.0. This session covers the progress made since the previous conference in 2023. You can watch the full recording below.&lt;/p&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/xiPJDIbUU0o" title="Lightning Talk: What&amp;#x27;s New in Jupyter Frontends - Jeremy Tuloup, QuantStack &amp;amp; Rosio Reyes, Anaconda" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thank you to all the new contributors, the Jupyter Frontends team hopes to see you back!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @Ankitaupadhaya, @CrafterKolyan, @EtiennePelletier, @Frank-Steiner, @Meriem-BenIsmail, @arjxn-py, @bnavigator, @bsundaram1, @dnlzrgz, @dotnetautor, @gjmooney&lt;/li&gt;
&lt;li&gt;Notebook: @honnix, @jchen1223, @sangampaudel530&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And thank you to returning contributors for their continued work!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @Darshan808, @DenisaCG, @HaudinFlorence, @MUFFANUJ, @RRosio, @SylvainCorlay, @achhina, @afshin, @andrii-i, @bollwyvl, @brichet, @choldgraf, @cmarmo, @davidbrochart, @ianthomas23, @isabela-pf, @itsmevichu, @jasongrout, @jrdnbradford, @jtpio, @kathatherine, @kr-2003, @krassowski, @mahendrapaipuri, @martinRenou, @minrk, @nkn2022, @peytondmurray, @ritzdevp, @robertstrauss, @rschroll, @shreve, @skapin, @trungleduc, @yacchin1205, @yukiiii04, @zanieb&lt;/li&gt;
&lt;li&gt;Notebook: @choldgraf, @itsmevichu, @jasongrout, @jtpio, @krassowski, @minrk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to all the people who tested and provided feedback for the pre-releases! Lastly, thank you to the weekly triage group for triaging JupyterLab, Notebook and JupyterLab Desktop issues: @RRosio, @krassowski, @andrii-i, @danyeaw among others who attend.&lt;/p&gt;
</content><category term="AI"/><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>JupyterLab 4.4 and Notebook 7.4 are available!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/" rel="alternate"/><published>2025-05-21T09:54:00+00:00</published><updated>2025-05-21T09:54:00+00:00</updated><author><name>Jeremy Tuloup</name></author><id>tag:jasongrout.github.io,2025-05-21:/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/</id><summary type="html">&lt;p&gt;JupyterLab 4.4 has been released! This new minor release of JupyterLab includes many new features and bug fixes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; 4.4 has been released! This new minor release of JupyterLab includes 28 new features and enhancements, 76 bug fixes, 63 maintenance tasks and 34 documentation improvements. There were 56 contributors to this release.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter/notebook"&gt;Jupyter Notebook&lt;/a&gt; 7.4 has also been released, including many of the fixes and enhancements in this JupyterLab 4.4 release. While the releases on GitHub provide additional detail on the updates made to both &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.4.0"&gt;JupyterLab 4.4&lt;/a&gt; and &lt;a href="https://github.com/jupyter/notebook/releases/tag/v7.4.0"&gt;Notebook 7.4&lt;/a&gt;, this article highlights some particularly notable changes.&lt;/p&gt;
&lt;p&gt;Extension authors can refer to the &lt;a href="https://jupyterlab.readthedocs.io/en/stable/extension/extension_migration.html#jupyterlab-4-3-to-4-4"&gt;Extension Migration Guide&lt;/a&gt; for information on API updates and details regarding changes to core packages.&lt;/p&gt;
&lt;h2 id="code-console-improvements"&gt;Code console improvements&lt;/h2&gt;
&lt;p&gt;The code console prompt can now be positioned on the top, left, or right side of the console, in addition to the default bottom placement. The placement is persisted in the Prompt Cell Position setting. The console toolbar now includes buttons to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute code&lt;/li&gt;
&lt;li&gt;Restart the kernel&lt;/li&gt;
&lt;li&gt;Clear the cells&lt;/li&gt;
&lt;li&gt;Switch the kernel&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="Changing the position of the prompt cell in JupyterLab code consoles" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/images/001-0_xR1yXwkrzFar51Rh.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Changing the position of the prompt cell in JupyterLab code consoles&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Additional settings for console behavior have been added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear Code Content on Execute (enabled by default) — When disabled, the code submitted for execution remains in the code editor, allowing for further edits.&lt;/li&gt;
&lt;li&gt;Hide Code Input — When enabled, only the execution output is shown in the console.&lt;/li&gt;
&lt;li&gt;Clear Cells on Execute — When enabled, only the last cell is displayed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Toggling all the three new settings transforms the console into an interactive editor resembling an ephemeral notebook with a single cell.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The code console configured as a single executable cell" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/images/002-0_yzStobKzh8gMimoc.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The code console configured as a single executable cell&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="settings-import-and-export"&gt;Settings import and export&lt;/h2&gt;
&lt;p&gt;Settings can now be exported to &lt;code&gt;overrides.json&lt;/code&gt; from the Settings Editor, which can be used to pre-configure defaults in deployments or to restore settings.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Import and export settings from the Settings Editor" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/images/003-0_MaZubqod8W1g-JHz.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Import and export settings from the Settings Editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="workspace-indicator"&gt;Workspace indicator&lt;/h2&gt;
&lt;p&gt;An opt-in workspace indicator is displayed on the top bar (next to the menu bar). It can be enabled in the &lt;code&gt;Settings → View → Appearance → Show Workspace Indicator&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The workspace indicator" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/images/004-0_9RGYIRu0foogfJC-.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The workspace indicator&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="support-for-collaboration-without-rtc-drive"&gt;Support for collaboration without &lt;code&gt;RTC:&lt;/code&gt; drive&lt;/h2&gt;
&lt;p&gt;Real time collaboration in previous versions of JupyterLab (which can be enabled by installing jupyter-collaboration package) was implemented by swapping the default JupyterLab file system drive to a collaborative drive with the &lt;code&gt;RTC:&lt;/code&gt; prefix. This approach was incompatible with multiple extensions. JupyterLab 4.4 introduces the Content Providers API, which allows the next version of &lt;code&gt;jupyter-collaboration&lt;/code&gt; to work without changing the drive prefix. This improves compatibility with extensions that do not support arbitrary drives.&lt;/p&gt;
&lt;h2 id="kernel-subshells-support"&gt;Kernel subshells support&lt;/h2&gt;
&lt;p&gt;Kernel subshells, introduced in &lt;a href="https://jupyter.org/enhancement-proposals/91-kernel-subshells/kernel-subshells.html"&gt;JEP 91&lt;/a&gt;, enable concurrent code execution in kernels that support them. When performing long-running computations (such as training a model), subshells enable users to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;ipywidgets&lt;/code&gt; with updates displayed immediately&lt;/li&gt;
&lt;li&gt;Monitor kernel resource usage (e.g. memory usage)&lt;/li&gt;
&lt;li&gt;Invoke arbitrary commands (e.g., refine code for a subsequent cell) while maintaining live access to previously defined variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Subshells are supported by &lt;code&gt;ipykernel&lt;/code&gt; 7 (currently in alpha).&lt;/p&gt;
&lt;p&gt;To create a subshell, open context menu (right-click) over a notebook using a kernel supporting subshells and select “New Subshell Console for Notebook”. A new console panel will open allowing access to variables from the main session.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Creating a new subshell console for a notebook in JupyterLab 4.4" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/jupyterlab-4-4-and-notebook-7-4-are-available/images/005-0_Ivi-4TMFnn-lcz4S.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Creating a new subshell console for a notebook in JupyterLab 4.4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;JupyterLab can automatically create subshells for comm messages if kernels support this feature. This enables concurrent execution of &lt;code&gt;ipywidgets&lt;/code&gt; callbacks without requiring a user action. This behavior can be changed from the settings, with options to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;disable that feature to not use subshells for comms&lt;/li&gt;
&lt;li&gt;(default) automatically create one subshell per comm-target (e.g. one subshell for all &lt;code&gt;ipywidgets&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;automatically create one subshell per comm (this can lead to issues if creating many comms)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context-menu-opt-out"&gt;Context menu opt-out&lt;/h2&gt;
&lt;p&gt;The context menu, available under right mouse click in browsers, can now be disabled globally by toggling the &lt;code&gt;Enable Context Menu&lt;/code&gt; option available in the Command Palette, or via the JSON Settings Editor in the Application Context Menu.&lt;/p&gt;
&lt;h2 id="slimmer-status-bar"&gt;Slimmer status bar&lt;/h2&gt;
&lt;p&gt;The terminals counter no longer appears in the status bar if no terminals are open. The kernels counter can be disabled by toggling the &lt;code&gt;Show the status bar item&lt;/code&gt; setting in &lt;code&gt;Running Terminals and Kernels&lt;/code&gt; (code name &lt;code&gt;showStatusBarItem&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="walkthrough-video"&gt;Walkthrough video&lt;/h2&gt;
&lt;p&gt;If you prefer a more interactive tour of the new features mentioned in the post, check out the video below:&lt;/p&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/YhYor8LWeJY" title="What&amp;#x27;s new in JupyterLab 4.4 and Notebook 7.4" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thank you to all the contributors (list below is based on GitHub usernames):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @Adam-D-Lewis @afshin @ajbozarth @AmberArr @andrewfulton9 @andreytaboola @andrii-i @bollwyvl @brichet @Carreau @claytonparnell @cmarmo @Darshan808 @davidbrochart @deephbz @dlqqq @echarles @fcollonval @fleming79 @holzman @hxrshxz @ianthomas23 @iisakkirotko @itsmevichu @JasonWeill @jesuino @joaopalmeiro @jtpio @kellyrowland @krassowski @kuraga @maitreya2954 @martenrichter @martinRenou @mgeier @MUFFANUJ @nkn2022 @Nriver @pawel99k @pdarshane @peytondmurray @pre-commit-ci @Princekumarofficial @Rishab87 @rpwagner @RRosio @rsaditya01 @SamuelMarks @SatyajitRedekar @SylvainCorlay @trungleduc @Zsailer&lt;/li&gt;
&lt;li&gt;Notebook: @andrii-i @jdavid @jtpio @krassowski @RRosio&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Special thank you to all the new contributors, the Jupyter Frontends team hopes to see you back!&lt;/p&gt;
&lt;p&gt;Thank you to all the people who tested and provided feedback for the pre-releases! Lastly, thank you to the weekly triage group for triaging JupyterLab, Notebook and JupyterLab Desktop issues: @krassowski, @RRosio, @andrii-i, @danyeaw among others who attend.&lt;/p&gt;
</content><category term="AI"/><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Building AI Agents for JupyterLab using Notebook Intelligence</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/" rel="alternate"/><published>2025-02-24T18:03:00+00:00</published><updated>2025-02-24T18:03:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2025-02-24:/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/</id><summary type="html">&lt;p&gt;It is now possible to build AI Agents for JupyterLab and access from Copilot Chat UI, using Notebook Intelligence!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/notebook-intelligence/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI) is an AI coding assistant and extensible AI framework for JupyterLab. (&lt;em&gt;For an introduction to NBI see&lt;/em&gt; &lt;a href="/posts/2025/introducing-notebook-intelligence/"&gt;&lt;em&gt;Introducing Notebook Intelligence&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and for basics of extending NBI see&lt;/em&gt; &lt;a href="https://notebook-intelligence.github.io/notebook-intelligence/blog/2025/02/05/building-ai-extensions-for-jupyterlab.html"&gt;&lt;em&gt;Building AI Extensions for JupyterLab&lt;/em&gt;&lt;/a&gt; &lt;em&gt;blog posts.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;GitHub Copilot and other AI coding assistants are great at generating code and answering coding related questions. But they can do a lot more than generating text and code thanks to LLM features such as tool calling and AI agents. NBI provides an extensible AI framework to integrate tool calling and AI agents into JupyterLab Copilot Chat.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="AI Agent extension example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/001-1_x9r8gIqX1wbubuE8sT6GFg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;AI Agent extension example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-is-tool-calling-and-an-ai-agent"&gt;What is tool calling and an AI Agent?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; is a feature of LLMs. It lets you introduce your own functions to LLM so that they can be called in response to chat prompts. LLM can convert natural language prompts to function calls with arguments. Tool calls are executed on the client side (i.e. Jupyter server) by your extension and only the function schema is provided to the LLM. Tool calling lets LLM interact with real time data, proprietary or external apps and services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI Agents&lt;/strong&gt; are collections of tools that can run tasks on behalf of the user. Given a natural language prompt, LLMs can reason, create an execution plan and call multiple tools in a chain. NBI provides a framework to build these type of AI Agent integrations and handles the orchestration between LLMs and your tools.&lt;/p&gt;
&lt;h2 id="ai-agent-extension-example"&gt;AI Agent Extension Example&lt;/h2&gt;
&lt;p&gt;Let’s build an AI Agent for JupyterLab using Notebook Intelligence extension APIs. (&lt;em&gt;The full source code for this extension is&lt;/em&gt; &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;&lt;em&gt;available here&lt;/em&gt;&lt;/a&gt;.) This will be an AI agent for map creation and notebook sharing. It will have the following capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Looking up geo-coordinates for an address&lt;/li&gt;
&lt;li&gt;Showing maps centered at an address in the Copilot Chat UI&lt;/li&gt;
&lt;li&gt;Creating notebooks that show maps centered at specified addresses&lt;/li&gt;
&lt;li&gt;Sharing notebooks publicly using &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tasks above will be run by the AI Agent in response to natural language prompts by the user.&lt;/p&gt;
&lt;p&gt;For this extension we will build four tools that will be integrated into JupyterLab Copilot Chat, for each of the tasks above. Tools are defined as classes derived from NBI &lt;code&gt;Tool&lt;/code&gt; abstract class. A tool needs to implement the methods and properties defined in this base class.&lt;/p&gt;
&lt;p&gt;Tool class provides the metadata information for the tool and implements the &lt;code&gt;pre_invoke&lt;/code&gt; and &lt;code&gt;handle_tool_call&lt;/code&gt; methods. &lt;code&gt;pre_invoke&lt;/code&gt; method is called right before &lt;code&gt;handle_tool_call&lt;/code&gt; with the tool arguments and it gives an opportunity for the tool to prompt for confirmation of the tool execution.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;schema&lt;/code&gt; property of the Tool is the function schema based on OpenAI’s function calling schema. It lets you describe your function and its parameters as an object. A Tool is expected to return an object as response from the &lt;code&gt;handle_tool_call&lt;/code&gt; method call.&lt;/p&gt;
&lt;h2 id="geo-coordinates-lookup-tool"&gt;Geo Coordinates Lookup Tool&lt;/h2&gt;
&lt;p&gt;This tool looks up geo-coordinates for an address using &lt;a href="https://github.com/geopy/geopy"&gt;Nominatim&lt;/a&gt; library. &lt;code&gt;pre_invoke&lt;/code&gt; method for this tool only shows a message in Chat UI before looking up for the geo-coordinates in &lt;code&gt;handle_tool_call&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;GeoCoordinateLookupTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;geo_coordinate_lookup&amp;quot;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Get geo-coordinates from an address&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;This is a tool that converts an address to a geo-coordinates&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;parameters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Address to convert to geo-coordinates&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;required&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;additionalProperties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Getting coordinates for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geolocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Get geo-coordinates tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/002-1_5qScMVUaGlIWWF7_F36Fsg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Get geo-coordinates tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-response-generator-tool"&gt;Map Response Generator Tool&lt;/h2&gt;
&lt;p&gt;This tool shows a map in Copilot Chat UI centered at geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, this tool returns a &lt;code&gt;HTMLFrame&lt;/code&gt; response that uses HTML to show a map centered at the requested location using Google Maps.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapResponseGeneratorTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Showing a map centered at latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTMLFrameData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&amp;lt;iframe width=&amp;quot;100%&amp;quot; height=&amp;quot;100%&amp;quot; frameborder=&amp;quot;0&amp;quot; scrolling=&amp;quot;no&amp;quot; marginheight=&amp;quot;0&amp;quot; marginwidth=&amp;quot;0&amp;quot; id=&amp;quot;gmap_canvas&amp;quot; src=&amp;quot;https://maps.google.com/maps?width=400&amp;amp;amp;height=400&amp;amp;amp;hl=en&amp;amp;amp;q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;t=&amp;amp;amp;z=11&amp;amp;amp;ie=UTF8&amp;amp;amp;iwloc=B&amp;amp;amp;output=embed&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I showed the map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Below is an example prompt showing map centered at “Golden Gate Bridge, San Francisco”. Note that an address was provided to Copilot as the input but Map Response Generator Tool accepts only geo-coordinates as input. This is where LLM automatically decided that it needs to first call the Geo Coordinates Lookup Tool to get geo-coordinates for this address and then it called the Map Response Generator Tool with the geo-coordinates. LLM automatically chained multiple tools and NBI handled this chaining to get the correct response for the user’s prompt.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Map Response Generator Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/003-1_6o_XrO-44kKaGYmEefsXKQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Response Generator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-notebook-creator-tool"&gt;Map Notebook Creator Tool&lt;/h2&gt;
&lt;p&gt;This tool creates a notebook centered at the specified geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, the tool creates a notebook using &lt;code&gt;nbformat&lt;/code&gt; library, saves it to disk and then opens the notebook in JupyterLab UI using the &lt;code&gt;response.run_ui_command&lt;/code&gt; NBI method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapNotebookCreatorTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating a map notebook for latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;map_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;_%H%M%S&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.ipynb&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_notebook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        ### This map notebook was created by an AI Agent using [Notebook Intelligence](https://github.com/notebook-intelligence)&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;install_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s2"&gt;capture&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%pip install folium&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;map_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        import folium&lt;/span&gt;

&lt;span class="s2"&gt;        map = folium.Map(location=[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;], zoom_start=13)&lt;/span&gt;
&lt;span class="s2"&gt;        map&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cells&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_markdown_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;install_code_cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_code_cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kernelspec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;python3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_ui_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docmanager:open&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I created and opened the map notebook&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Map Notebook Creator Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/004-1_2BZGe7aGPnNTPY79ZNhUDQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Notebook Creator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="notebook-share-tool"&gt;Notebook Share Tool&lt;/h2&gt;
&lt;p&gt;This tool shares a notebook publicly by uploading it to &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt; and displays the link to the shared notebook.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;pre_invoke&lt;/code&gt; method implementation, this tool asks for confirmation first as this operation is an undoable share of the notebook publicly. Only after the user confirms, &lt;code&gt;handle_tool_call&lt;/code&gt; is executed. In &lt;code&gt;handle_tool_call&lt;/code&gt; method the tool uploads the notebook at the &lt;code&gt;notebook_file_path&lt;/code&gt; using &lt;a href="https://github.com/notebook-sharing-space/nbss-upload"&gt;nbss_upload&lt;/a&gt; library and then shows the link to the shared notebook on &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;NotebookShareTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sharing notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationTitle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Confirm sharing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationMessage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Are you sure you want to share the notebook at &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;? This will upload the notebook to public internet and cannot be undone.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;share_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbss_upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_notebook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://notebooksharing.space&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnchorData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Click here to view the shared notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; has been shared at: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Notebook Share Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/005-1_CYXJbXzmMGnuW_c_ktwkgA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Notebook Share Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="tool-call-schema-definitions"&gt;Tool call schema definitions&lt;/h2&gt;
&lt;p&gt;It is important to define schemas of the tools clearly and disambiguate the tools as much as possible so that the LLM can invoke the proper tool based on the user prompt. LLM parses the user prompt, decides which tools to call and generates the input parameters for the call.&lt;/p&gt;
&lt;p&gt;If current file or selection is made visible by the user, NBI can provide the file paths and content as context to the LLM. That way LLM can use those as additional context for a tool call. That is how Notebook Share Tool was able to access the current notebook file.&lt;/p&gt;
&lt;h2 id="tool-chaining"&gt;Tool chaining&lt;/h2&gt;
&lt;p&gt;After parsing the user prompt, LLM creates an execution plan and can call multiple tools in a chain. NBI handles this tool chaining for you. It is important to define your schemas with the chaining in mind. Consider defining matching tool outputs and inputs so that the output of a tool can be passed onto another one directly if needed.&lt;/p&gt;
&lt;p&gt;Notice that in this extension example MapResponseGeneratorTool and MapNotebookCreatorTool both take in geo_coordinates (latitude, longitude) as input and GeoCoordinateLookupTool outputs geo_coordinates. This lets LLM to directly pass the output of GeoCoordinateLookupTool to MapResponseGeneratorTool and MapNotebookCreatorTool. It also lets a user to use an address to trigger MapResponseGeneratorTool and MapNotebookCreatorTool, because LLM knows that there is another tool it can call to generate input (geo_coordinates) from address for these tools.&lt;/p&gt;
&lt;h2 id="chat-participant"&gt;Chat Participant&lt;/h2&gt;
&lt;p&gt;In NBI AI framework, AI Agents are defined as chat participants and tools are tied to specific chat participants. For our extension we create &lt;code&gt;AIAgentChatParticipant&lt;/code&gt; as our participant (for more details on NBI extensions and chat participants see &lt;a href="http://127.0.0.1:4000/notebook-intelligence/blog/2025/02/04/building-ai-extensions-for-jupyterlab.html"&gt;this blog&lt;/a&gt;). Our chat participant returns list of tools it defines from the &lt;code&gt;tools&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;handle_chat_request&lt;/code&gt; method our chat participant passes the request to the base &lt;code&gt;ChatParticipant&lt;/code&gt; class to handle tool calling for us.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ChatParticipant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@property&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ai-agent&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@property&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Tool&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;GeoCoordinateLookupTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MapResponseGeneratorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MapNotebookCreatorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NotebookShareTool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_chat_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_chat_request_with_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="nbi-extension"&gt;NBI Extension&lt;/h2&gt;
&lt;p&gt;Finally we create our NBI extension class &lt;code&gt;AIAgentExtension&lt;/code&gt;. This class basically registers our chat participant to NBI on extension activation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotebookIntelligenceExtension&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_chat_participant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AI Agent example extension activated&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is all there is to create an AI Agent for JupyterLab using Notebook Intelligence. The &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;full source code&lt;/a&gt; for this example is available along with installation instructions for you to use as a reference and/or build on top.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;I am looking forward to seeing the AI Agents built by the community. Please try the extension APIs and share your feedback using project’s &lt;a href="https://github.com/notebook-intelligence/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="JupyterLab"/></entry><entry><title>Introducing Notebook Intelligence!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/" rel="alternate"/><published>2025-01-13T16:28:00+00:00</published><updated>2025-01-13T16:28:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2025-01-13:/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/</id><summary type="html">&lt;p&gt;I am thrilled to announce the release of Notebook Intelligence! NBI is an AI coding assistant for JupyterLab powered by GitHub Copilot…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I am thrilled to announce the release of &lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI)! NBI is an AI coding assistant and extensible AI framework for JupyterLab. It uses &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; under the hood and is inspired by its design principles. NBI greatly boosts the productivity of JupyterLab users with AI assistance powered by GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/001-1_X4bZbN5zydCMu6AJV3bP7A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="generate-code-iterate-on-it"&gt;Generate code, iterate on it&lt;/h2&gt;
&lt;p&gt;NBI integrates tightly with the notebook document. Using cell toolbar item “Generate code” or keyboard shortcut “Cmd + G” / “Ctrl + G”, you can launch the inline coding assistant popover to generate code cells.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code popover" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/002-1_WnT4YBV94oGF_qvT7rpMZg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code popover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the inline coding assistant is launched for a cell with existing code, then the generated code is shown in a diff view for approval.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code with diff viewer" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/003-1_4hM3aV5c3HycBY2HWQSaBQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code with diff viewer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you are not satisfied with the generated code, you can re-generate with an updated prompt. Diff viewer also lets you edit the generated code manually before accepting it.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Fix code in cell with NBI" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/004-1_8GgXLlEYjGtHNNw_m1VK4A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Fix code in cell with NBI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="explain-and-fix-code-troubleshoot-errors-reported"&gt;Explain and fix code, troubleshoot errors reported&lt;/h2&gt;
&lt;p&gt;NBI adds a new sub menu to notebook cell context menu. Copilot can explain code in a cell or suggest fixes for any issues in it. Clicking these menu items opens Copilot Chat and generates a suggestion.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot context menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/005-1_ItUuGiP1VvhgxQ4BzBDb5w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot context menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the code cell has an output you can ask Copilot to explain it. If there are any errors reported, you can have Copilot to troubleshoot as well. These actions also take you to Copilot Chat interface.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Explain, fix, troubleshoot" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/006-1_X7c10sZzH5qbv5kpAUOMBA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Explain, fix, troubleshoot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="inline-completions"&gt;Inline Completions&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence integrates with JupyterLab’s inline completion APIs and provides code suggestions as you type in a code cell or a Python file.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/007-1_EG7lvC6mjTyHLi6mcAGEIQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Code suggestions are generated using GitHub Copilot. They are blazing fast and relevant to document you are working on. In addition to the code cell you are working on, the code in the surrounding cells are also used as context when generating suggestions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/008-1_BkIHAJsG4Fs7-8aD3k-4GA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="copilot-chat"&gt;Copilot Chat&lt;/h2&gt;
&lt;p&gt;NBI provides a user friendly chat interface to chat with GitHub Copilot. You can ask questions related to coding.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot Chat" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/009-1_ebEfHbxI6JnOWu9sSgP-nA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot Chat&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If Copilot generates code snippets, they are rendered in a special format in a section with an action toolbar. Toolbar will have buttons to copy, insert, create new Python file and notebook from the snippet. Using these you can easily integrate the generated code into your project.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot chat toolbar actions" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/010-1_aAOuJZrObHMhZ2aaxhWRuA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot chat toolbar actions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="chat-commands"&gt;Chat Commands&lt;/h3&gt;
&lt;p&gt;Chat interface also provides commands to generate new notebooks and Python code files based on your task described in the prompt. Commands start with “/” and a command auto-complete list is shown as you type. You can navigate between the commands using keyboard and choose a suggestion using “Enter” or “Tab” keys.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Chat command auto-complete" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/011-1_WBLVr1CtBxhVpMr2KYPVuQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Chat command auto-complete&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newnotebook-command"&gt;/newNotebook command&lt;/h3&gt;
&lt;p&gt;You can generate new notebooks from a prompt with the &lt;strong&gt;/newNotebook&lt;/strong&gt; command. Notebook generation is shown interactively, a new empty notebook is created and opened, then code and markdown cells are added onto the notebook as they are generated by NBI and GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate notebook example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/012-1_Dkhv6nZt2vExa-UjMTkaRA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate notebook example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newpythonfile-command"&gt;/newPythonFile command&lt;/h3&gt;
&lt;p&gt;You can also create new Python files from a prompt using the &lt;strong&gt;/newPythonFile&lt;/strong&gt; command.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate Python file example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/013-1_H6fNVs50mmQcsYlTAE17bg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate Python file example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="getting-started-with-notebook-intelligence"&gt;Getting Started with Notebook Intelligence&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence is a JupyterLab extension published as a Python package. Simply install the package and restart JupyterLab. NBI will add a new sidebar item for Copilot Chat, a notebook context sub-menu, a cell toolbar item for “Generate code” and a status bar item for GitHub Copilot login status to JupyterLab UI. It will also be integrated with inline completion (AI suggestions for code completion).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;notebook-intelligence
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="authentication-with-github-copilot"&gt;Authentication with GitHub Copilot&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence requires a &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; subscription. NBI provides a user friendly interface to sign into your GitHub Copilot account from JupyterLab UI to activate access to your subscription.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="GitHub Copilot authentication" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/014-1_5qQDXByT8dHSHMs86geeiQ.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;GitHub Copilot authentication&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="extensible-framework"&gt;Extensible Framework&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence provides APIs to let developers extend its capabilities. You can add custom agents / chat participants, define tools (function calling) and add RAG capabilities to provide your own context to LLM for code / chat response generation. Stay tuned for my next blog post where I will walk you though extensibility features.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; is currently in beta and designed for Python (support for more languages coming soon). Please try it out and share your feedback and any feature requests using project’s &lt;a href="https://github.com/mbektas/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="JupyterLab"/></entry><entry><title>JupyterLab 4.3 and Notebook 7.3 are available!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/" rel="alternate"/><published>2024-12-05T22:41:00+00:00</published><updated>2024-12-05T22:41:00+00:00</updated><author><name>Jeremy Tuloup</name></author><id>tag:jasongrout.github.io,2024-12-05:/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/</id><summary type="html">&lt;p&gt;JupyterLab 4.3.0 and Notebook 7.3.0 have been released! These new minor releases include many enhancements and bug fixes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; 4.3.0 has been released! This new minor release of JupyterLab includes 1 new feature, 42 enhancements, 62 bug fixes, 48 maintenance tasks and 35 documentation improvements. There were 38 contributors to this release including 18 new contributors to JupyterLab.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter/notebook"&gt;Jupyter Notebook&lt;/a&gt; 7.3.0 has also been released, including many of the fixes and enhancements in this JupyterLab 4.3.0 release. While the releases on GitHub provide additional detail on the updates made to both &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.3.0"&gt;JupyterLab 4.3&lt;/a&gt; and &lt;a href="https://github.com/jupyter/notebook/releases/tag/v7.3.0"&gt;Notebook 7.3&lt;/a&gt;, this article highlights some particularly notable changes.&lt;/p&gt;
&lt;p&gt;Extension authors can refer to the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#jupyterlab-4-2-to-4-3"&gt;Extension Migration Guide&lt;/a&gt; for information on API updates and details regarding changes to core packages.&lt;/p&gt;
&lt;h2 id="minimap"&gt;Minimap&lt;/h2&gt;
&lt;p&gt;The virtual scrollbar now acts as a minimap and exposes information on whether a cell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is running/scheduled (&lt;code&gt;[*]&lt;/code&gt;), idle (&lt;code&gt;[ ]&lt;/code&gt;) or was already run (e.g. &lt;code&gt;[1]&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;was modified since it was last executed (“dirty”) — orange background,&lt;/li&gt;
&lt;li&gt;has an output that contains an error — red background,&lt;/li&gt;
&lt;li&gt;is markdown/code (&lt;code&gt;[ ]&lt;/code&gt;/&lt;code&gt;[*]&lt;/code&gt;/&lt;code&gt;[1]&lt;/code&gt; for code, nothing for markdown),&lt;/li&gt;
&lt;li&gt;an how much text/code it contains.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To enable the minimap, click on the hamburger icon (≡) which appears in the notebook toolbar. If you do not see the hamburger icon, go to Settings → Notebook → Windowing mode and choose “full”.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The notebook minimap in JupyterLab 4.3" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/images/001-0_ylz6jx3MOOD7NqFb.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The notebook minimap in JupyterLab 4.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="fullscreen-mode"&gt;Fullscreen mode&lt;/h2&gt;
&lt;p&gt;JupyterLab now offers two ways to toggle the full screen mode from within the application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;using the View → Appearance → Fullscreen Mode menu entry,&lt;/li&gt;
&lt;li&gt;using a customizable shortcut, by default F11 (custom shortcuts are supported in Chrome, Safari and Edge).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="file-browser-enhancements"&gt;File browser enhancements&lt;/h2&gt;
&lt;p&gt;The file browser now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;supports resizing the columns and remembers the column sizes after reloading JupyterLab,&lt;/li&gt;
&lt;li&gt;supports uploading folders by drag-and-drop,&lt;/li&gt;
&lt;li&gt;supports navigation with a single click (opt-in in JupyterLab, default in Jupyter Notebook),&lt;/li&gt;
&lt;li&gt;has the file filter collapsed by default (funnel icon), increasing the space available.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="File browser enhancements in JupyterLab 4.3" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/images/002-0_vi_Hia43dNGLxQxD.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;File browser enhancements in JupyterLab 4.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="server-side-execution-and-collaboration"&gt;Server-side execution and collaboration&lt;/h2&gt;
&lt;p&gt;JupyterLab 4.2 added the option for extensions to implement server-side execution, which preserves outputs created when the browser window is closed or disconnected. The server-side execution builds on the same shared models API as the real-time collaboration (RTC).&lt;/p&gt;
&lt;p&gt;You can try these features by installing extensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/datalayer/jupyter-server-nbmodel"&gt;jupyter-server-nbmodel&lt;/a&gt;* provides server-side execution for jupyter-server,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/jupyter-collaboration"&gt;jupyter-collaboration&lt;/a&gt; provides the real-time collaboration features,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyter-server/jupyverse"&gt;jupyverse&lt;/a&gt; is an alternative server (a replacement for jupyter-server) which supports server-side execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;* while jupyter-server-nbmodel is developed by maintainers of JupyterLab, as of the JupyterLab 4.3.0 release date it is not an official Project Jupyter extension.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="improved-kernel-and-server-interactions"&gt;Improved kernel and server interactions&lt;/h2&gt;
&lt;p&gt;The previous release enabled connecting to external kernels, such as those spawned by a third-party application like Blender. In this release the kernel selector dialog was improved to also show the external kernels.&lt;/p&gt;
&lt;p&gt;The previous release improved the awareness of the kernel/kernel specification distinction by providing both tree and list view in the kernels sidebar panel, but only the tree view featured the kernel identifiers. In this release both the list and tree view show the kernel identifier stub.&lt;/p&gt;
&lt;p&gt;A new toolbar button was added in the kernel allowing to shut down unused kernels (kernels with no widget attached).&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Shut down unused kernels in JupyterLab 4.3" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/images/003-0_sZAliNNu3C2wamFo.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Shut down unused kernels in JupyterLab 4.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="full-width-notebook"&gt;Full Width Notebook&lt;/h2&gt;
&lt;p&gt;In Jupyter Notebook 7.3, it is now possible to configure the notebook to take up the full width of the page via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a setting that can be defined by a user via the Settings Editor&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;View &amp;gt; Enable Full Width Notebook&lt;/code&gt; menu entry&lt;/li&gt;
&lt;li&gt;the command palette, searching for &lt;code&gt;Enable Full Width Notebook&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="Full width notebook in Jupyter Notebook 7.3" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-3-and-notebook-7-3-are-available/images/004-0_6FYCiWSCRcxPNeVP.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Full width notebook in Jupyter Notebook 7.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="jupyterlab-3-end-of-maintenance"&gt;JupyterLab 3 end of maintenance&lt;/h2&gt;
&lt;p&gt;As a reminder, &lt;strong&gt;JupyterLab 3 will reach its end of maintenance date on December 31, 2024, anywhere on Earth&lt;/strong&gt;. Users are highly encouraged to update to JupyterLab 4 as soon as possible.&lt;/p&gt;
&lt;p&gt;For more information, please refer to the &lt;a href="/posts/2024/jupyterlab-3-end-of-maintenance/"&gt;announcement post&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thank you to all the new contributors, the Jupyter Frontends team hopes to see you back!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @jgoodson, @Wzixiao, @krishanbhasin-px, @hnben, @sanskriti2005, @achhina, @mahendrapaipuri, @Renegade334, @Nazeeh21, @pauldruce, @EdsterG, @gderocher, @dhysdrn, @algonell, @nekomeowww, @mjpsyapse, @kaushalaaditya99, @Nagidrop&lt;/li&gt;
&lt;li&gt;Notebook: @bluestealth, @andyscho, @algonell, @gjmooney&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And thank you to returning contributors for their continued work!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @afshin, @ajbozarth, @andrewfulton9, @andrii-i, @brichet, @cmarmo, @Darshan808, @davidbrochart, @fcollonval, @gabalafou, @HaudinFlorence, @itsmevichu, @JasonWeill, @joaopalmeiro, @jtpio, @krassowski, @maitreya2954, @Mehak261124, @NexVeridian, @RRosio&lt;/li&gt;
&lt;li&gt;Notebook: @JasonWeill, @jtpio, @krassowski, @martinRenou, @RRosio&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to all the people who tested and provided feedback for the pre-releases! Lastly, thank you to the weekly triage group for triaging JupyterLab, Notebook and JupyterLab Desktop issues: @JasonWeill, @krassowski, @RRosio, @andrii-i among others who attend.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Exploring a Document’s Timeline in JupyterLab</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/exploring-a-documents-timeline-in-jupyterlab/" rel="alternate"/><published>2024-09-13T19:15:00+00:00</published><updated>2024-09-13T19:15:00+00:00</updated><author><name>Meriembenismail</name></author><id>tag:jasongrout.github.io,2024-09-13:/medium-archive/pelican/posts/2024/exploring-a-documents-timeline-in-jupyterlab/</id><summary type="html">&lt;p&gt;Introducing a document timeline component for JupyterLab&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In collaborative environments, keeping track of changes and understanding the evolution of a document is crucial. This is especially true in fields like scientific computing and data science, where multiple contributors work together on complex projects. To address this need, we’re excited to introduce a document timeline component, a new feature that ships with the &lt;a href="https://jupyterlab-realtime-collaboration.readthedocs.io/en/latest/"&gt;Jupyter Collaboration&lt;/a&gt; extension and enhances how teams interact with shared documents.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;timeline component&lt;/strong&gt; is designed to give users control over a document’s history. Positioned as a slider in the JupyterLab status bar, it allows users to navigate through the timeline of a document. You can explore past versions, compare changes over time without altering the original document, and even restore specific versions. This feature is compatible with all types of shared documents within Jupyter, including notebooks, text files, and even third-party document types like &lt;a href="https://github.com/jupytercad/JupyterCAD"&gt;&lt;strong&gt;JupyterCAD&lt;/strong&gt;&lt;/a&gt; models.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Exploring the history of a Jupyter Notebook with the timeline component." src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/exploring-a-documents-timeline-in-jupyterlab/images/001-1_iGMClCbnpOvUlZ5xoiAASA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Exploring the history of a Jupyter Notebook with the timeline component&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="how-does-it-work"&gt;How Does it Work ?&lt;/h2&gt;
&lt;p&gt;Co-editing of notebooks and other documents was introduced in JupyterLab 3.1 and has been improved and consolidated in subsequent releases. The feature can be enabled by installing the &lt;a href="https://jupyterlab-realtime-collaboration.readthedocs.io/en/latest/"&gt;Jupyter Collaboration&lt;/a&gt; extension, which is built upon the &lt;a href="https://github.com/yjs/yjs"&gt;&lt;strong&gt;Yjs&lt;/strong&gt;&lt;/a&gt; framework, an implementation of CRDTs (Conflict-free Replicated Data Types). An important byproduct enabling real-time collaboration was the introduction of a journal of updates capturing all changes made by collaborators, which we leveraged to implement document timeline navigation.&lt;/p&gt;
&lt;p&gt;Two key changes to Jupyter collaboration underlie the timeline component:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ability to &lt;em&gt;fork&lt;/em&gt; the document history, which is required to visualize the past state of the document without altering it.&lt;/li&gt;
&lt;li&gt;The support of Undo/Redo operations in the Python implementation of &lt;strong&gt;Yjs&lt;/strong&gt;, &lt;a href="https://jupyter-server.github.io/pycrdt/"&gt;&lt;strong&gt;PyCRDT&lt;/strong&gt;&lt;/a&gt;, through the implementation of an &lt;a href="https://github.com/jupyter-server/pycrdt/blob/main/python/pycrdt/_undo.py"&gt;undo manager&lt;/a&gt;. While this was already available in &lt;strong&gt;Yjs&lt;/strong&gt;, it was missing in &lt;strong&gt;PyCRDT&lt;/strong&gt; until now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you move the slider to go back in time, the first operation that takes place is &lt;strong&gt;forking&lt;/strong&gt; the document. Forking creates a new branch of the document at the selected point in history. This approach ensures that the most recent version of the document remains unaltered while you explore earlier versions. The forked version essentially acts as a sandbox where you can navigate through the document’s history, test out changes, and decide on the best course of action without modifying the main document.&lt;/p&gt;
&lt;p&gt;When you navigate through the document’s timeline using the slider, the undo manager allows you to reverse or reapply any series of changes, stepping back through the document’s history &lt;strong&gt;without&lt;/strong&gt; losing any information. If you decide to move forward again, the undo manager reapplies those changes in the exact order they were originally made, preserving the document’s integrity.&lt;/p&gt;
&lt;p&gt;After reviewing the timeline, you can choose to restore a particular version at a selected timestamp. This restored version becomes the new active state of the document, effectively merging the changes from the forked branch back into the main document.&lt;/p&gt;
&lt;p&gt;The document timeline component in the status bar offers an intuitive, flexible way to manage document history. Users can confidently experiment with different versions of their work, knowing that every action is reversible and that no progress will be lost. Whether you’re refining a piece of code, iterating on a design, or reviewing past contributions, the timeline equips you to iterate efficiently and effectively.&lt;/p&gt;
&lt;h2 id="seamless-integration-with-jupytercad"&gt;Seamless Integration with JupyterCAD&lt;/h2&gt;
&lt;p&gt;The document timeline is not just limited to text-based documents or notebooks. It was designed in a document-agnostic way, allowing its usage for any collaborative document. Consequently, it integrates seamlessly with &lt;a href="https://github.com/jupytercad/JupyterCAD"&gt;&lt;strong&gt;JupyterCAD&lt;/strong&gt;&lt;/a&gt;, JupyterLab’s extension for creating and manipulating 3D models. For engineers and designers, this integration is a game-changer.&lt;/p&gt;
&lt;p&gt;In complex engineering projects, design iterations are common, and the ability to track the evolution of a 3D model is crucial. With the document timeline, users can effortlessly navigate through different stages of their design process. If a recent change in the 3D model doesn’t yield the desired results, the document timeline allows you to slide back to a previous version and pick up your work from there.&lt;/p&gt;
&lt;p&gt;By combining the power of &lt;strong&gt;JupyterCAD&lt;/strong&gt; with the document timeline’s history management, a team can achieve a more dynamic and flexible design process, ensuring that every idea is captured and every change is reversible. This integration exemplifies how the Jupyter Collaboration extension is enhancing not just collaborative editing but also specialized workflows in engineering and design.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Exploring the history of a JupyterCAD document with the timeline component." src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/exploring-a-documents-timeline-in-jupyterlab/images/002-1_0hhaIizZhuUGbSRCcsuZLA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Exploring the history of a JupyterCAD document with the timeline component&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="a-new-era-of-collaboration-in-jupyter"&gt;A New Era of Collaboration in Jupyter&lt;/h2&gt;
&lt;p&gt;The document timeline is a significant step forward in our ongoing work to make Jupyter a user-friendly platform for collaborative technical computing. By leveraging the advanced capabilities of the &lt;strong&gt;PyCRDT&lt;/strong&gt; &lt;a href="https://github.com/jupyter-server/pycrdt/blob/main/python/pycrdt/_undo.py"&gt;undo manager&lt;/a&gt;, we’ve created a tool that simplifies document history management and enhances the overall collaborative experience.&lt;/p&gt;
&lt;p&gt;As we continue to innovate and expand the Jupyter Collaboration extension, we invite the community to try out the new document timeline feature and share their feedback. Together, we can further refine these tools and ensure that Jupyter remains at the forefront of collaborative computing, empowering users across all disciplines to achieve more.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="acknowledgments"&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;The development of the document timeline and its integration into the Jupyter Collaboration extension has been a true team effort, made possible by the dedication and expertise of many individuals. Special thanks are extended to the following team members for their invaluable contributions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;David Brochart :&lt;/strong&gt; For their innovative approach to integrating the &lt;a href="https://github.com/jupyter-server/pycrdt/blob/main/python/pycrdt/_undo.py"&gt;undo manager&lt;/a&gt; within &lt;strong&gt;PyCRDT&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Afshin T. Darian :&lt;/strong&gt; For their supervision and feedback, which have been crucial in refining the functionality of the timeline component.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duc Trung Le :&lt;/strong&gt; For their expertise in &lt;a href="https://github.com/jupytercad/JupyterCAD"&gt;&lt;strong&gt;JupyterCAD&lt;/strong&gt;&lt;/a&gt; and for ensuring that the timeline component seamlessly integrates with 3D model workflows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/meriem-ben-ismail-163042230/"&gt;Meriem Ben Ismail&lt;/a&gt; just completed her Software Engineering degree at &lt;a href="https://insat.rnu.tn"&gt;INSAT&lt;/a&gt; (National Institute of Applied Sciences and Technology in Tunisia) and her six-month internship as an open-source scientific software engineer at QuantStack.&lt;/p&gt;
</content><category term="collaboration"/><category term="JupyterLab"/></entry><entry><title>JupyterLab 4.2 and Notebook 7.2 are available!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/" rel="alternate"/><published>2024-05-22T21:00:00+00:00</published><updated>2024-05-22T21:03:00+00:00</updated><author><name>Rosio Reyes</name></author><id>tag:jasongrout.github.io,2024-05-22:/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/</id><summary type="html">&lt;p&gt;JupyterLab 4.2.0 has been released! This new minor release of JupyterLab includes 3 new features, 20 enhancements, 33 bug fixes and 29…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; 4.2.0 has been released! This new minor release of JupyterLab includes 3 new features, 20 enhancements, 33 bug fixes and 29 maintenance tasks. There were 39 contributors to this release including 10 new contributors to JupyterLab.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter/notebook"&gt;Jupyter Notebook&lt;/a&gt; 7.2.0 has also been released, including many of the fixes and enhancements in this JupyterLab 4.2.0 release. While the releases on Github provide additional detail on the updates made to both &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.2.0"&gt;JupyterLab 4.2&lt;/a&gt; and &lt;a href="https://github.com/jupyter/notebook/releases/tag/v7.2.0"&gt;Notebook 7.2&lt;/a&gt;, this article highlights some particularly notable changes.&lt;/p&gt;
&lt;p&gt;Extension authors can refer to the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#jupyterlab-4-1-to-4-2"&gt;Extension Migration Guide&lt;/a&gt; for information on API updates and details regarding changes to core packages.&lt;/p&gt;
&lt;h2 id="easier-workspaces-management-with-gui"&gt;&lt;strong&gt;Easier Workspaces Management with GUI&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;It is now possible to manage workspaces from within JupyterLab with the workspaces graphical user interface. This &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/6944"&gt;long awaited feature&lt;/a&gt; offers users an additional way to interact with workspaces beyond the existing URL and CLI-based options, providing a more user-friendly and accessible alternative for some.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab 44.2 Workspaces Sidebar" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/images/001-1_vEiDh6zMT9nlBX3oNDK-pw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab 4.2.0 Workspaces UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="recently-openedclosed-files"&gt;&lt;strong&gt;Recently opened/closed files&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Jupyterlab-recents, an extension for tracking recent files and directories has now been integrated into core JupyterLab. This extension adds a main menu entry for recently opened/closed files. JupyterLab users now have this native functionality that provides a quick reference to files they have recently worked with. Further development work in JupyterLab core now also allows users to search for and access recently closed files and folders through a modal via the shortcut &lt;em&gt;Ctrl + Alt + A&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab 4.2.0 Recently Closed Submodal" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/images/002-1_0p77Tr6helRfOkwBIEt5Mg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab 4.2.0 Recently Closed Submodal&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="full-notebook-windowing-mode-by-default"&gt;&lt;strong&gt;Full notebook windowing mode by default&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The previously available full notebook windowing mode has now been made the default mode. This mode renders only the cells visible in the window, leading to improved performance. With this mode using the JupyterLab search function is recommended due to false negatives with the web browser’s native search functionality.&lt;/p&gt;
&lt;h2 id="improved-shortcuts-editor"&gt;&lt;strong&gt;Improved Shortcuts Editor&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Users can enjoy an improved experience when customizing shortcuts. Various improvements have been made to the shortcuts editor, namely: default shortcuts can be deleted, and shortcuts are sorted appropriately when using a language pack. Entries for shortcuts with varying arguments are also now accurately presented as separate items.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Delete shortcut key binding option in modal." src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/images/003-1_z8boqvL968A1NbhHbIDAdA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Modal option to delete shortcut key binding.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="dark-high-contrast-theme"&gt;&lt;strong&gt;Dark High Contrast Theme&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The JupyterLab Dark High Contrast Theme is now available for users who might benefit from higher contrast as outlined by the WCAG AAA accessibility standards for color contrast.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab 4.2 Dark High Contrast Theme" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-2-and-notebook-7-2-are-available/images/004-1_A-q4OYlI3gptT2CyU_mzoA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab 4.2.0 Dark High Contrast Theme&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="keyboard-shortcuts"&gt;&lt;strong&gt;Keyboard Shortcut&lt;/strong&gt;s&lt;/h2&gt;
&lt;p&gt;The shortcuts extension received some improvements based on user feedback. The &lt;em&gt;Ctrl + D&lt;/em&gt; shortcut has been restored to allow users to use to delete a line. In the interest of code editor compatibility the &lt;em&gt;Ctrl + Shift + K&lt;/em&gt; shortcut can still be used for deleting lines and multi-cursor selection is now available via the default shortcut: &lt;em&gt;Ctrl + Shift + D&lt;/em&gt;. Users are now able to toggle line comments with the shortcut &lt;em&gt;Ctrl + /&lt;/em&gt; and toggle block comments with &lt;em&gt;Alt + A&lt;/em&gt;, both of which are customizable via the Settings Editor. This release also addresses &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/15744"&gt;issues&lt;/a&gt; related to using various keys on international keyboards in macOS.&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thank you to all the new contributors, we hope to see you back!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @linlol, @erkin98, @mdietz94, @UntitledError-09, @Susilkessav, @Tanmay-Deshmukh, @AnastasiaSliusar, @milinm, @Rob-P-Smith, @NexVeridian.&lt;/li&gt;
&lt;li&gt;Notebook: @afeld&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And thank you to returning contributors for their continued work!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @davidbrochart, @fcollonval, @FoSuCloud, @JasonWeill, @jtpio, @krassowski, @Alanhou1222, @divyansshhh, @itsmevichu, @kolibril13, @sawickid, @andrii-i, @bollwyvl, @brichet, @Mehak261124, @e218736, @echarles, @gabalafou, @m158261, @afshin, @minrk, @RRosio, @trungleduc, @blink1073, @diyoyo, @AllanChain, @ericsnekbytes, @HaudinFlorence&lt;/li&gt;
&lt;li&gt;Notebook: @jtpio, @krassowski&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to all the people who tested and provided feedback for the pre-releases! Lastly, thank you to the weekly triage group for triaging JupyterLab, Notebook and JupyterLab Desktop issues: @JasonWeill, @krassowski, @RRosio among others who attend.&lt;/p&gt;
&lt;p&gt;Contributors to this blog post: @krassowski, @jtpio, @JasonWeill, @RRosio.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>JupyterLab Desktop UI Modes</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/" rel="alternate"/><published>2024-03-11T21:09:00+00:00</published><updated>2024-03-11T21:09:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-03-11:/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/</id><summary type="html">&lt;p&gt;We are pleased to announce UI Mode features recently added to JupyterLab Desktop (JLD). JupyterLab Desktop now provides a convenient way to…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce UI Mode features recently added to JupyterLab Desktop (JLD). JupyterLab Desktop now provides a convenient way to set the JupyterLab UI layout per project and introduces an new layout mode: “Zen Mode”.&lt;/p&gt;
&lt;h3 id="ui-mode-menu"&gt;UI Mode menu&lt;/h3&gt;
&lt;p&gt;There is now a new sub-menu under session menu, titled “UI Mode”, which lets you change the UI layout mode. You can choose one of the pre-defined modes or leave the layout management to the web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="UI Mode menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/001-1_YPG0bV4tNk2-LWmd3LrNcg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;UI Mode menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you select “Zen Mode”, “Single document IDE” or “Multi document IDE” for a project, UI layout will update to the selected mode and it will also be reset to this mode next time the project is opened. Even if you made modifications to the layout using JupyterLab View menu, UI layout will still reset when the project is reopened. You can choose “Managed by web app” option if you would like the selections on View menu to persist.&lt;/p&gt;
&lt;p&gt;“Reset to session default” option resets the UI mode to either to default of “UI Mode for opening a single file” or “UI Mode” as defined in the settings dialog (described in UI Mode configuration below) based on the number of files opened by the session.&lt;/p&gt;
&lt;h3 id="multi-document-ide-mode"&gt;Multi document IDE mode&lt;/h3&gt;
&lt;p&gt;This mode is the default layout mode in JupyterLab web app. Activity bar and side bar on the left, activity bar on the right and status bar are visible in this mode. Multiple documents and terminals can be opened at the same time.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Multi document IDE mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/002-1_7r_IE0ATYbHnwJ1-d_xb1Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Multi document IDE mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="single-document-ide-mode"&gt;Single document IDE mode&lt;/h3&gt;
&lt;p&gt;This mode corresponds to the “Simple Interface“ mode in JupyterLab web app. Activity bar, side bar and status bar visibilities are the same as “Multi document IDE” mode but tab bar is not shown and only one document or terminal can be open at a time.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Single document IDE mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/003-1_av6nSBGg052bbsM8aRAXTg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Single document IDE mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="zen-mode"&gt;Zen Mode&lt;/h3&gt;
&lt;p&gt;“Zen Mode” is a newly introduced mode in JLD which simplifies “Simple Interface” further by hiding activity bars, side bars and status bar. Zen Mode is great for focusing on a notebook document. It is the new default mode for opening a single notebook or creating a new notebook in JLD.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Zen Mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/004-1_zRM-Fi9MT-S3WGq0mN-R2w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Zen Mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Below is a demo of “Zen Mode” in action. Notice that using menus or keyboard shortcuts you can still very easily navigate between different notebooks and terminals.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Zen Mode demo" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/005-1_ZmM2yk2twSA01msHOZU2sg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Zen Mode demo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="ui-mode-configuration"&gt;UI Mode configuration&lt;/h3&gt;
&lt;p&gt;Default UI Mode for sessions and for opening a single notebook can be configured from the Settings dialog as shown below. Setting both options to “Manage by web app” makes JLD behave the same as the JupyterLab web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="UI Mode configuration" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/006-1_Q6vDiTD9ct3_7sBMUbvDrw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;UI Mode configuration&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>Python environment management using JupyterLab Desktop CLI</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/" rel="alternate"/><published>2024-03-01T20:41:00+00:00</published><updated>2024-03-01T20:41:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-03-01:/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/</id><summary type="html">&lt;p&gt;JupyterLab Desktop CLI provides several commands and options to manage Python environments for use in the application…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;JupyterLab Desktop (JLD) CLI provides several commands and options to manage Python environments for use in the application. In my &lt;a href="/posts/2024/python-environment-management-in-jupyterlab-desktop/"&gt;previous blog post&lt;/a&gt; I had covered using JLD UI to manage Python environments. You can use &lt;strong&gt;jlab&lt;/strong&gt; CLI commands to do the same and for additional environment management options.&lt;/p&gt;
&lt;h3 id="setting-up-jlab-cli"&gt;Setting up jlab CLI&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop installers for Windows and Linux create &lt;strong&gt;jlab&lt;/strong&gt; CLI command as part of the installation process. On macOS, the CLI command is created at first program launch. Since this requires elevated user permissions, users may need to do a one time manual approval on macOS UI. Visit Settings dialog on macOS to verify that CLI command is installed properly.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab CLI status on Settings dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/001-1_HS9E-UDLEIo2U0qvMFngeA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab CLI status on Settings dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Setting up CLI is only an operation to configure the command for use in the Terminal. It doesn’t install any additional software, the functionality is already built into the desktop app.&lt;/p&gt;
&lt;h3 id="jlab-env-cli-commands"&gt;jlab env CLI commands&lt;/h3&gt;
&lt;p&gt;“jlab env &lt;action&gt; [options]” CLI commands provide environment management functionality. Below are the list of actions supported with overview of each one. Each action can have a variety of parameter options. See the Python environment management &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/python-env-management.md#python-environment-management-using-cli"&gt;CLI documentation&lt;/a&gt; on JLD GitHub repo for the details on parameter options.&lt;/p&gt;
&lt;p&gt;Under the hood, these commands use conda and pip that are available on your system. conda and pip could be pre-installed or they become available with the bundled Python environment installation.&lt;/p&gt;
&lt;h3 id="jlab-env-info"&gt;jlab env info&lt;/h3&gt;
&lt;p&gt;This command prints app’s Python environment configuration on the system. JLD initializes this config at launch time by discovering the Python configuration on your system. These settings can be updated by using the UI or CLI commands that are listed below.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env info command output" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/002-1_KRDJFM_g3Jauf4FO3cxxiw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env info command output&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-list"&gt;jlab env list&lt;/h3&gt;
&lt;p&gt;Lists discovered and user set Python environments available to the app. Python paths, python and jupyterlab package versions and whether it was created by JLD are also listed.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env list command output" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/003-1_lgXUXSxJYoR82UQoWe4XbQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env list command output&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-create"&gt;jlab env create&lt;/h3&gt;
&lt;p&gt;Creates a new Python environment and adds to the application registry, making it ready to use by the app. Environments can be created by using the bundled environment installer or by downloading packages from registry. “jupyterlab” Python package is required for compatibility with JLD and it is automatically installed in most cases. Additional Python packages and conda channels to use can also be specified.&lt;/p&gt;
&lt;p&gt;This CLI command provides more options compared to Python environment management UI. Additional environment sources can be used. You can create an environment from a pre-archived conda-pack bundle, conda-lock file or a conda environment file in addition to options available on the UI. These source files can be local file paths on the system or remote URLs.&lt;/p&gt;
&lt;p&gt;Using remote sources, environments can be created using archives and environment definition files created by conda-store and similar tools.&lt;/p&gt;
&lt;p&gt;Additional list of packages can be installed on top of packages defined in an environment definition file. Below is an example of creating a numpy environment using a remote environment definition file and installing scikit-learn in addition.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env create commad example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/004-1_-PEjCqxG5nEddcZRVulleg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env create commad example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-activate"&gt;jlab env activate&lt;/h3&gt;
&lt;p&gt;Activates a Python environment in system Terminal. Environments can be activated by name or full prefix path.&lt;/p&gt;
&lt;h3 id="jlab-env-set-python-envs-path"&gt;jlab env set-python-envs-path&lt;/h3&gt;
&lt;p&gt;Sets Python environment install directory used when creating new environments.&lt;/p&gt;
&lt;h3 id="jlab-env-set-conda-path"&gt;jlab env set-conda-path&lt;/h3&gt;
&lt;p&gt;Sets the base conda executable path. Base conda executable is used when creating new conda environments, and running conda commands by the app.&lt;/p&gt;
&lt;h3 id="jlab-env-set-conda-channels"&gt;jlab env set-conda-channels&lt;/h3&gt;
&lt;p&gt;Sets conda channels to use when installing new conda packages.&lt;/p&gt;
&lt;h3 id="jlab-env-set-system-python-path"&gt;jlab env set-system-python-path&lt;/h3&gt;
&lt;p&gt;Sets Python executable path to use when creating new venv environments.&lt;/p&gt;
&lt;h3 id="jlab-env-update-registry"&gt;jlab env update-registry&lt;/h3&gt;
&lt;p&gt;Updates the app environment registry. This command checks all the environments registered for compatibility and fetches Python and jupyterlab versions. This action is normally done at desktop app launch.&lt;/p&gt;
&lt;h3 id="setting-project-python-environments-using-cli"&gt;Setting project Python environments using CLI&lt;/h3&gt;
&lt;p&gt;You can set the Python environment used for a project very easily by using the Python environment selection popup on the UI. You can also set it by using the CLI command “jlab config set”.&lt;/p&gt;
&lt;p&gt;Below is an example command to set a project’s Python environment using jlab CLI.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab config set commad to set project Python path" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/005-1_hJV8jjrhpQFsq019FV3AqA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab config set commad to set project Python path&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="additional-resources"&gt;Additional resources&lt;/h3&gt;
&lt;p&gt;For additional details on environment management CLI commands see the &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/python-env-management.md#python-environment-management-using-cli"&gt;Python environment management CLI documentation&lt;/a&gt; at JupyterLab Desktop GitHub repo. Command parameters and options with plenty of examples can be found there.&lt;/p&gt;
&lt;p&gt;JLD provides other CLI commands to launch and configure the application. Check out the &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/cli.md"&gt;CLI documentation&lt;/a&gt; for other available commands.&lt;/p&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>JupyterLab 4.1 and Notebook 7.1 are here 🎉</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/" rel="alternate"/><published>2024-02-26T22:57:00+00:00</published><updated>2024-02-28T17:47:00+00:00</updated><author><name>Project Jupyter</name></author><id>tag:jasongrout.github.io,2024-02-26:/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/</id><summary type="html">&lt;p&gt;JupyterLab 4.1 and Notebook 7.1 are now available! These releases include several new features, bug fixes, and enhancements for extension…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; 4.1 and &lt;a href="https://github.com/jupyter/notebook"&gt;Notebook&lt;/a&gt; 7.1 are now available! These releases include several new features, bug fixes, and enhancements for extension developers. This release is compatible with extensions supporting JupyterLab 4.0 and Notebook 7.0.&lt;/p&gt;
&lt;p&gt;JupyterLab 4.1 is one of the largest minor releases of JupyterLab to date, bringing 6 new features, 39 enhancements, and 114 bug fixes to users, and addressing 140 maintenance tasks. Project Jupyter thanks the 70 contributors, including 13 new code/documentation contributors, who helped us build this new version.&lt;/p&gt;
&lt;p&gt;Jupyter Notebook 7.1 is the first minor release after &lt;a href="/posts/2023/announcing-jupyter-notebook-7/"&gt;the transition to the new codebase&lt;/a&gt;, which re-uses JupyterLab components. As such, Notebook 7.1 inherits many of the new features and fixes from JupyterLab 4.1. Feature parity with Notebook 6 was also improved in this release.&lt;/p&gt;
&lt;p&gt;JupyterLab Desktop was also upgraded to version 4.1, receiving additional bug fixes and enhancements. Thank you to Mehmet Bektas for your continuing work to improve JupyterLab Desktop!&lt;/p&gt;
&lt;p&gt;Extension authors should consult the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#jupyterlab-4-0-to-4-1"&gt;Extension Migration Guide&lt;/a&gt;, which lists deprecations and changes to the public API.&lt;/p&gt;
&lt;h2 id="custom-css"&gt;Custom CSS&lt;/h2&gt;
&lt;p&gt;JupyterLab now supports automatic loading of custom CSS. &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_dev.html#theme-plugins"&gt;Themes&lt;/a&gt; are the recommended way for customizing the JupyterLab look and feel, while custom CSS is intended for minor personal adjustments.&lt;/p&gt;
&lt;p&gt;To opt in, start JupyterLab with the — custom-css flag. The location of the custom.css file is documented in the section on &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/interface_customization.html#custom-css"&gt;customizing the user interface&lt;/a&gt;. Please note that the CSS selectors may vary between versions and applications (JupyterLab vs Notebook).&lt;/p&gt;
&lt;h2 id="diagrams-in-markdown"&gt;Diagrams in Markdown&lt;/h2&gt;
&lt;p&gt;Matching GitHub-Flavoured Markdown, JupyterLab and Notebook now support &lt;a href="https://github.com/mermaid-js/mermaid"&gt;Mermaid&lt;/a&gt; diagrams. To create a Mermaid diagram, use the mermaid language specifier for a code block in a markdown cell or document, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;```mermaid
flowchart LR

A[Hard] --&amp;gt;|Text| B(Round)
B --&amp;gt; C{Decision}
C --&amp;gt;|One| D[Result 1]
C --&amp;gt;|Two| E[Result 2]
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which renders as:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/001-0_PpZ4a6IMZOkvxz7W.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="inline-code-completion"&gt;Inline code completion&lt;/h2&gt;
&lt;p&gt;JupyterLab and Notebook now support automatic code (and text) completion presented as ghost text in the cell and file editors, allowing generative AI models to provide multi-line completions. The suggestions are provided by plugins implementing the IInlineCompletionProvider API. By default a single provider using the user’s kernel history is available.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/002-0_xMRyjiyaizpZ33vE.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The suggestions can be invoked as-you-type or manually using a configurable shortcut (by default Alt + ). The default keyboard shortcuts are displayed in the small widget shown when hovering over the ghost suggestion.&lt;/p&gt;
&lt;p&gt;To enable the inline suggestions based on the kernel history, go to Settings → Settings Editor → Inline Completer → History provider → check the “enabled” checkbox.&lt;/p&gt;
&lt;p&gt;In addition to the built-in history suggestions, additional inline completion providers can be installed. For example, the &lt;a href="https://github.com/jupyterlab/jupyter-ai"&gt;jupyter-ai&lt;/a&gt; extension, version &lt;a href="https://github.com/jupyterlab/jupyter-ai/releases/tag/v2.10.0"&gt;2.10.0&lt;/a&gt; and newer, provides suggestions from compatible large language models.&lt;/p&gt;
&lt;p&gt;The Inline Completer API is still considered experimental and may be subject to change. Please share your feedback!&lt;/p&gt;
&lt;h2 id="keyboard-navigation-improvements"&gt;Keyboard navigation improvements&lt;/h2&gt;
&lt;p&gt;Numerous improvements to keyboard navigation with focus on accessibility and usability are included in this release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the notebook cells now retain focus&lt;/li&gt;
&lt;li&gt;the focus can now be moved beyond the active notebook&lt;/li&gt;
&lt;li&gt;the toolbars can now be navigated using arrow keys&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details, see &lt;a href="/posts/2023/recent-keyboard-navigation-improvements-in-jupyter/"&gt;the post on the Jupyter Blog about keyboard navigation improvements&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="execution-history-in-notebook"&gt;Execution history in notebook&lt;/h2&gt;
&lt;p&gt;The code from previously executed cells can be used to populate empty cells, letting users iterate on code from previous cells or even previous sessions, depending on how a specific kernel stores its history.&lt;/p&gt;
&lt;p&gt;To cycle between history items, press Alt + Arrow Up and Alt + Arrow Down.&lt;/p&gt;
&lt;p&gt;To enable execution history, go to Settings Editor → Notebook → check the “Kernel history access” checkbox.&lt;/p&gt;
&lt;p&gt;This feature was already available in the console in previous releases; it only works with kernels supporting execution history requests. To clear the execution history, consult the documentation of the kernel you are using (e.g., IPython/ipykernel).&lt;/p&gt;
&lt;h2 id="opening-files-from-tracebacks"&gt;Opening files from tracebacks&lt;/h2&gt;
&lt;p&gt;Paths to code files detected in error tracebacks are now turned into links. These links will open the corresponding file for editing, if it is in the Jupyter root directory, or they will open a read-only preview if the file is outside of the root directory and the active kernel supports the debugger.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/003-0_9RAGHslzMNiejAyi.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="error-indicator-in-the-table-of-contents"&gt;Error indicator in the table of contents&lt;/h2&gt;
&lt;p&gt;When a cell fails during execution, an error indicator will be displayed by the corresponding heading, increasing awareness of the notebook state and enabling users to quickly navigate to the cell which requires attention.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/004-0_LvMrjrwtXoQas0KA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="plugin-manager"&gt;Plugin Manager&lt;/h2&gt;
&lt;p&gt;Individual plugins can now be disabled or enabled from a new Plugin Manager user interface. While the existing extension manager can enable/disable entire extensions, each extension is composed of one or more plugins (and plugins form the basis of JupyterLab itself) thus the plugin manager enables more extensive customization of the JupyterLab experience in addition to the previously available extension manager.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/005-0_xFUeQMS4Ul12H5JG.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;This feature is intended for advanced users and is documented in depth in the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/extensions.html#managing-plugins-with-plugin-manager"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Administrators may want to &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/extensions.html#locking-and-unlocking-plugins"&gt;lock specific plugins&lt;/a&gt; if they are required for any reason; this will prevent users from disabling the plugins via Plugin Manager and remote API calls. The Plugin Manager itself can be &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/extensions.html#enabling-and-disabling-extensions"&gt;disabled using the CLI&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="virtual-scrollbar-for-notebook-in-windowed-mode"&gt;Virtual scrollbar for notebook in windowed mode&lt;/h2&gt;
&lt;p&gt;The windowed notebook now has an optional scrollbar that shows the active cell and selected cells. Users can jump to a specific cell.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-4-1-and-notebook-7-1-are-here/images/006-0_XIIHUorBt2_HPYIh.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;To enable the virtual scrollbar, go to Settings → Notebook → Windowing mode, choose “full”, and click on the hamburger icon (≡) which appears in the notebook’s toolbar.&lt;/p&gt;
&lt;p&gt;Virtual scrollbar is an experimental feature. Please share your feedback!&lt;/p&gt;
&lt;h2 id="notifications"&gt;Notifications&lt;/h2&gt;
&lt;p&gt;JupyterLab 3.6 added a notification center which so far was only used for announcements and version update notifications (both opt-in). JupyterLab 4.1 adds two notifications to guide users in potentially confusing situations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when a user attempts to save a read-only document, a transient notification suggesting using “save as” is displayed&lt;/li&gt;
&lt;li&gt;when a user attempts to execute a cell before a slow-starting kernel has initialized, a notification is shown to indicate that the cell cannot be yet executed (this is opt-in and needs to be enabled in settings)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="full-notebook-windowing-mode-improvements"&gt;Full notebook windowing mode improvements&lt;/h2&gt;
&lt;p&gt;Notebooks in the full windowing mode only render the visible cells, significantly improving the performance of the application. Numerous improvements for the full windowing mode behavior (such as scrolling, search, rendering, and navigation) are included in this release (see the list of issues in &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/15258"&gt;#15258&lt;/a&gt; for details). Note: the windowing mode is still experimental and &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/15258"&gt;known issues&lt;/a&gt; remain to be solved.&lt;/p&gt;
&lt;h2 id="search-improvements"&gt;Search improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The search box will now grow automatically to accommodate longer text&lt;/li&gt;
&lt;li&gt;Search in selection can now be toggled using Alt + L and automatic search in selection can be configured in settings&lt;/li&gt;
&lt;li&gt;Tooltips with shortcuts were added to the buttons in the search box to improve discoverability of the shortcuts&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Special shout out to new code/documentation contributors (hope to see you again!):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @alden-ilao @AllanChain @ashna1jain @brijsiyag @DcWire @DenisaCG @Deepali1211 @dhml @dolevf @eliaslma @emmanuel-ferdman @e4e @g547315 @jans-code @j264415 @kiliansinger @KiranmaiKalla @misterfads @mmichilot @mdengler @MFA-X-AI @m158261@nbowditch-einblick @nluetts @paolocarinci @pauky @paulkim3151 @phil-zxx @Rmarieta @RRosio @Sarthug99 @sinistersnare @t03857785 @Wh1isper&lt;/li&gt;
&lt;li&gt;Notebook: @Dilip-Jain @haok1402 @akx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to the returning contributors (please do feel invited to review pull requests as well!):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JupyterLab: @afshin @akx @andrewfulton9 @andrii-i @bollwyvl @bikash30851 @brichet @davidbrochart @divyansshhh @dharmaquark @DonJayamanne @echarles @ericsnekbytes @fcollonval @firai @FoSuCloud @gabalafou @hbcarlos @JasonWeill @jtpio @krassowski @mctoohey @minrk @nishikantparmariam @parmentelat @skyetim @smacke @SylvainCorlay @telamonian @tibdex @timkpaine @trungleduc @yuvipanda&lt;/li&gt;
&lt;li&gt;Notebook: @brichet @jtpio&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and everyone who tested the pre-releases!&lt;/p&gt;
&lt;p&gt;Additionally, we would like to thank our community triage leaders who helped sort issues for JupyterLab (@JasonWeill), Notebook (@RRosio) and JupyterLab Desktop during the course of &lt;a href="https://github.com/jupyterlab/team-compass?tab=readme-ov-file#weekly-jupyter-triage-meeting"&gt;weekly triage meetings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Contributors to this blog post: @JasonWeill @krassowski @gabalafou @jtpio&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Python environment management in JupyterLab Desktop</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/" rel="alternate"/><published>2024-02-19T22:26:00+00:00</published><updated>2024-02-19T22:26:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-02-19:/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/</id><summary type="html">&lt;p&gt;We are pleased to announce Python environment management features recently added to JupyterLab Desktop. Now you can create new Python env…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce Python environment management features recently added to JupyterLab Desktop (JLD). Now you can create new Python environments and manage existing ones on your system, right from JupyterLab Desktop UI.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment management dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/001-1_OHi6WzYg6MXIxl8EyPqWkQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment management dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="python-environments-and-their-use-in-jupyterlab-desktop"&gt;Python environments and their use in JupyterLab Desktop&lt;/h3&gt;
&lt;p&gt;The standard distribution of JupyterLab is a Web Application which is available as a Python package named “jupyterlab”. JupyterLab Desktop makes installing and launching JupyterLab much easier by bundling it together with Python and other scientific computing dependencies in the form of a Python environment.&lt;/p&gt;
&lt;p&gt;Python environments provide isolation of package installations and they let you work on different projects with different set of packages and/or package versions. JLD comes with a bundled Python environment installer and lets users install it as the default environment for the app from the UI and effortlessly. It can also use existing Python environments on a system to launch JupyterLab web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Desktop — Python environment selection" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/002-1_h5A_Mqdns44zGQ9HIDglcA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Desktop — Python environment selection&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With the latest updates, JLD supports creating custom Python environments from the UI and the CLI. Bundled environment can now be installed to multiple locations, as different virtual environments. JLD also allows you to create new environments by downloading packages from conda or PyPI registries.&lt;/p&gt;
&lt;h3 id="why-support-environment-management-in-jupyterlab-desktop"&gt;Why support environment management in JupyterLab Desktop?&lt;/h3&gt;
&lt;p&gt;It is common for our users to use different Python environments for different projects as they may require different set of Python packages or package versions. Also, in order to use a Python environment in JLD, certain requirements need to be satisfied, such as existence of “jupyterlab” package. These are some of the motivations behind adding support for Python environment management to JupyterLab Desktop.&lt;/p&gt;
&lt;p&gt;Normally, creating a Python environment requires installing Python and/or conda onto the system first and getting familiar with terminal commands required to manage the environments. JLD simplifies this process by eliminating the need to make additional installations and by providing a user friendly interface, making it possible to create new environments with few UI clicks.&lt;/p&gt;
&lt;p&gt;conda and Python are shipped with JupyterLab Desktop within its bundled environment installer. Once the bundled environment is installed then JLD can use the bundled conda and Python to create new environments without requiring any further conda/Python installation from the user.&lt;/p&gt;
&lt;p&gt;Python environments created in JLD are compatible with the application itself and they are added to the environment registry of JLD automatically for use in sessions. This prevents dealing with compatibility issues and manually adding environments to JLD registry.&lt;/p&gt;
&lt;h3 id="python-environment-management-dialog"&gt;Python environment management dialog&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop now has a new dialog which lets you manage Python environments and settings. You can access this dialog by clicking “Manage Python environments” menu item on session menu (hamburger menu on top-right).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Managing existing environments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Environments” tab lists the environments discovered on your system. If you have other compatible environments available on your system but not discovered then you can add those by clicking “Add existing” button. You can choose the Python executable path in the system dialog that will be shown and the environment will be validated and added to JLD registry. Any validation errors will be shown above the environment list.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment context menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/003-1_SS4TOzXRuUo1KjtWLsu62w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment context menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can hover over the environment list to see additional details on the environment such as JupyterLab version. You can access actions for each environment by clicking the menu on the right end of the row. You can copy the Python path or detailed environment info to clipboard by using the first two menu items. Clicking “Launch Terminal” opens a system terminal and activates the selected environment. You can browse the environment’s file system by clicking “Reveal in Finder/Explorer” . “Delete” action is available for environments created by JLD. It lets you remove the environment installation from your system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creating new environments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Create new” tab lets you create new environments using the bundled installer or packages from registry.&lt;/p&gt;
&lt;p&gt;“Copy of the bundled environment” installs a copy of the bundled environment in a different location on your system. You can set a name for it and the installation path will be shown below. The parent directory for new environment installations can be configured as will be shown in the sections below.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create new environment using bundled installer" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/004-1_GvVlKNy1A9vMMfjPd_vYww.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create new environment using bundled installer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;“New environment” option lets you create a new environment from scratch by downloading packages from conda or PyPI registries based on your environment type selection. In order to be compatible with JLD, “jupyterlab” Python package is installed into the environment by default. You can uncheck “Include jupyterlab” if you don’t plan to use the environment with JLD. You can install additional Python packages into the environment using “Additional Python packages” field and by entering the list of packages separated by space. The preview of the environment create command will be shown below. You can click “Show output” during installation to see the progress or to inspect the output from installer.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create new environment using packages from registry" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/005-1_ozkNZe_GYy14tI4P2pc91A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create new environment using packages from registry&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Newly created environments will be listed in “Environments” tab and they will be available to use in JLD sessions immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python environment settings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Settings” tab lets you set Python environment configuration for JLD. The settings on this dialog are auto populated based on defaults or information discovered on your system.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment settings" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/006-1_oJQrdfgcD2YO2rzhuT7msw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The default Python environment to use for JLD can be set on the top. This setting used to be in the “Settings dialog” and moved here.&lt;/p&gt;
&lt;p&gt;“New Python environment install directory” defines the parent directory for newly created environments.&lt;/p&gt;
&lt;p&gt;“conda path” is the path of conda executable to use to create new conda environments and to activate conda sub environments.&lt;/p&gt;
&lt;p&gt;“conda channels” sets the channels to use when installing conda packages.&lt;/p&gt;
&lt;p&gt;Python path setting at the bottom controls which Python executable to use when creating new venv environments.&lt;/p&gt;
&lt;h3 id="updates-to-python-environment-selection-popup"&gt;Updates to Python environment selection popup&lt;/h3&gt;
&lt;p&gt;When local JupyterLab Desktop sessions are created, the active environment information is shown on top-right section of the title bar. This session info button is clickable and when clicked it shows the Python environment selection popup. New features are added to this popup with the latest release.&lt;/p&gt;
&lt;p&gt;On top row of this popup, the current Python path used in the session is shown now. There are two new buttons on the right of this path. The first one lets you restart the JupyterLab server running for the session. This could be useful to restart JupyterLab web app after installing packages, without restarting the whole app. The second button lets you copy session information to the clipboard, such as the URL of the JupyterLab server running.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Session environment info and action buttons" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/007-1_ulCxlvKfiJ84gFABBnJnaA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Session environment info and action buttons&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now you can filter the Python environments listed on the environment select popup. As you type into the path filter field, the environments will be filtered by the match in Python path.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Filter Python environments" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/008-1_TYPfThb5Ks1rBZDueXW-Jw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Filter Python environments&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Clicking the gear icon next to filter input field takes you to the Python environment management dialog.&lt;/p&gt;
&lt;h3 id="updating-bundled-python-environment-installation"&gt;Updating bundled Python environment installation&lt;/h3&gt;
&lt;p&gt;When JupyterLab Desktop is updated, the bundled Python environment installation is not updated automatically. There wasn’t a clear indication of this out of sync installation and users had to go to Settings dialog to manually update the bundled environment.&lt;/p&gt;
&lt;p&gt;Now with the latest version, notification badge is shown when the environment update is available and auto-update of the bundled environment installation made possible. You can configure the environment update options on “Advanced” tab of the “Settings dialog”. Note that “Update bundled environment automatically when app is updated” option will delete the existing installation and make a fresh install of the bundled environment.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update settings" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/009-1_QLH8DrYK4QKPMKlS_niQ5A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When there is an update available for the bundled environment installation a notification badge is shown on the session title bar with a red circle over the server icon.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update notification badge" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/010-1_5FtuxOZX2cxN5O70D_m9BQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update notification badge&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Also, a new action button is shown on environment selection popup on the right of the current Python path label, in orange color. You can update the bundled environment installation by simply clicking this button.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update button" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/011-1_zcPcX9XNoBC0b4KbkmassA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Bundled environment updates require app restart and update is installed before the next launch of the application.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update progress" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/012-1_plmarhiU7kTDrpBrpKzkiw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update progress&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="coming-up-next"&gt;Coming up next!&lt;/h3&gt;
&lt;p&gt;Stay tuned for my next blog post which will be on the Python environment management using JupyterLab Desktop CLI! CLI commands provide additional options for creating Python environments such as installation using environment definition files and conda-pack bundles.&lt;/p&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>JupyterLab 3 end of maintenance</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-3-end-of-maintenance/" rel="alternate"/><published>2024-02-19T22:07:00+00:00</published><updated>2024-02-19T22:07:00+00:00</updated><author><name>Project Jupyter</name></author><id>tag:jasongrout.github.io,2024-02-19:/medium-archive/pelican/posts/2024/jupyterlab-3-end-of-maintenance/</id><summary type="html">&lt;p&gt;The JupyterLab Council has agreed to an important change to JupyterLab’s version lifecycle. Each major version of JupyterLab will now…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The JupyterLab Council has agreed to an important change to &lt;a href="https://jupyterlab.readthedocs.io/en/latest/getting_started/lifecycle.html"&gt;JupyterLab’s version lifecycle&lt;/a&gt;. Each major version of JupyterLab will now receive updates until &lt;strong&gt;one year after the following major version’s first release&lt;/strong&gt;. JupyterLab 4.0.0 was released on May 15, 2023, so &lt;strong&gt;JupyterLab 3 will reach its end of maintenance date on May 15, 2024, anywhere on Earth&lt;/strong&gt;. To help us make this transition, fixes for critical issues will still be backported until December 31, 2024. If you are still running JupyterLab 3, we strongly encourage you to &lt;strong&gt;upgrade to JupyterLab 4 as soon as possible&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This represents a change from JupyterLab’s previous policy, by which we supported two major versions of JupyterLab at a time, and which would have obligated us to continue supporting JupyterLab 3 until JupyterLab 5.0.0 became generally available. We decided to make this change to let developers, contributors, and users focus their attention on the newest major version, JupyterLab 4. Maintaining multiple major versions requires additional time and effort, which could instead be put towards making the current version better for all users. Recent releases on the 3.6.x branches have included mainly security fixes and maintenance fixes, not new features.&lt;/p&gt;
&lt;p&gt;We recognize that there are many users still using JupyterLab 3, having downloaded it directly from package repositories or using it as distributed by a commercial vendor. We strongly encourage all users and vendors to upgrade to Lab 4 as soon as possible. If this is not possible, out of acknowledgement for the short notice of this announcement, &lt;strong&gt;we will consider pull requests addressing critical issues against the 3.6.x branch through December 31, 2024, anywhere on Earth&lt;/strong&gt;, provided that they meet &lt;em&gt;all&lt;/em&gt; of the following requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The fix addresses a critical issue: a security vulnerability, data loss, or another issue of very high severity.&lt;/li&gt;
&lt;li&gt;The fix includes tests, is reasonably small and low in complexity, and the author communicates actively with maintainers who review their work.&lt;/li&gt;
&lt;li&gt;The fix is approved by a JupyterLab maintainer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The JupyterLab team recognizes that a few regressions from JupyterLab 3 were present in JupyterLab 4.0.0; &lt;a href="https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aopen+is%3Aissue+label%3Atag%3ARegression+milestone%3A4.0.x"&gt;some of them are still open&lt;/a&gt;. Project Jupyter is driven by volunteers, community contributors, and support from individual and corporate users. We thank our community for their patience and we encourage our contributors to help us resolve bugs that block users from upgrading to the newer version. The JupyterLab maintainers are committing to review any pull requests submitted to address regressions as a priority over pull requests&lt;/p&gt;
&lt;p&gt;We want to hear from you! If you have questions or comments about this new version lifecycle, please leave them below, or join the conversation on the &lt;a href="https://discourse.jupyter.org/t/jupyterlab-3-end-of-maintenance/23867"&gt;Jupyter Community Forum&lt;/a&gt;. We also welcome discussion about the change at our weekly JupyterLab meetings, which are held every Wednesday at 09:00 US Pacific (17:00 UTC as of today, 16:00 UTC starting on March 10, 2024).&lt;/p&gt;
&lt;p&gt;Thank you for using JupyterLab and for being a part of our great global community!&lt;/p&gt;
</content><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Recent keyboard navigation improvements in Jupyter</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/recent-keyboard-navigation-improvements-in-jupyter/" rel="alternate"/><published>2023-12-16T09:48:00+00:00</published><updated>2023-12-16T09:48:00+00:00</updated><author><name>Project Jupyter</name></author><id>tag:jasongrout.github.io,2023-12-16:/medium-archive/pelican/posts/2023/recent-keyboard-navigation-improvements-in-jupyter/</id><summary type="html">&lt;p&gt;Towards a more accessible Jupyter Notebook&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Towards a more accessible Jupyter Notebook&lt;/p&gt;
&lt;p&gt;Upcoming versions of JupyterLab (4.1.0) and Notebook (7.1.0) will include major keyboard accessibility fixes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/keyboard-accessible"&gt;Keyboard accessibility&lt;/a&gt; is fundamental to overall app accessibility. Interactions that require pointing devices like mice or trackpads are usability barriers for many users across a wide spectrum of disabilities. Ensuring that all UI features are seamlessly accessible through keyboard-only navigation is imperative for an inclusive user experience.&lt;/p&gt;
&lt;h2 id="keyboard-navigation-in-jupyterlab"&gt;Keyboard navigation in JupyterLab&lt;/h2&gt;
&lt;p&gt;A recent audit of the JupyterLab UI highlighted significant gaps in keyboard navigation, posing obstacles to usability and accessibility. One of the main obstacles to efficient keyboard navigation in JupyterLab is the number of UI items to skim through before being able to perform an action, such as typing in a document or creating a file. Indeed, as with any feature-rich application, the JupyterLab UI includes many menus, widgets, and inputs to interact with. The user interface is made of three main areas (left, center, and right), each one split into several panels. And most of these panels include toolbars with buttons to perform specific actions.&lt;/p&gt;
&lt;p&gt;Improving this navigation required separate fixes for different elements of the page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Menu bar (&lt;a href="https://github.com/jupyterlab/lumino/pull/465"&gt;Lumino #465)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tab bar (&lt;a href="https://github.com/jupyterlab/lumino/pull/612"&gt;Lumino #612)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Toolbar (&lt;a href="https://github.com/jupyterlab/jupyterlab/pull/15021"&gt;JupyterLab #15021)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these changes follow the same logic: the Tab key is used to navigate between top-level elements (toolbar, menu, tab list), but not to navigate between items for which one should use arrow keys, nor to select an item for which one can use the Enter/Space keys. Most of these changes were actually applied to low-level components of JupyterLab, so that any JupyterLab extension built upon them will benefit from these improvements.&lt;/p&gt;
&lt;p&gt;The changes to the toolbar make use of a new &lt;a href="https://github.com/jupyterlab-contrib/jupyter-ui-toolkit"&gt;UI toolkit&lt;/a&gt;, based on web components, which natively includes these accessibility features, but also helps standardize the widgets used across the JupyterLab UI and JupyterLab extensions.&lt;/p&gt;
&lt;h2 id="notebook-tab-traps"&gt;Notebook Tab Traps&lt;/h2&gt;
&lt;p&gt;Another blocker to keyboard navigation was the existence of “tab traps” or “focus traps” in the notebook widget. Tab traps occur when a user cannot move focus away from an interactive element with the Tab key.&lt;/p&gt;
&lt;p&gt;The main problem was that, while the input area of each notebook cell could be reached by using the Tab key, the input area itself, which is a text editor, does not allow moving to the next item using the Tab key since the Tab key is used to insert spaces into the editor.&lt;/p&gt;
&lt;p&gt;This has been fixed in &lt;a href="https://github.com/jupyterlab/jupyterlab/pull/14115"&gt;JupyterLab PR #14115&lt;/a&gt;, by changing the tabbable items in the Notebook from the cell input to the cell itself. Now the Notebook follows the same logic mentioned above. Arrow keys are used to navigate the cells, whereas the Tab key is reserved mostly to navigate into and then back out of the Notebook. Entering or exiting the input element of the cell is done using the Enter and Escape keys, respectively.&lt;/p&gt;
&lt;p&gt;These changes required a complete inversion in the way the keyboard events are handled in the notebook, especially with respect to keyboard shortcuts. These modifications were required to retain keyboard shortcuts in the output widget and at the notebook level, while changing the way element focus is managed in the notebook.&lt;/p&gt;
&lt;h2 id="future-accessibility-improvements"&gt;Future accessibility improvements&lt;/h2&gt;
&lt;p&gt;Accessibility work at Jupyter is grounded in the recommendations of the &lt;a href="https://www.w3.org/WAI/"&gt;W3C Web Accessibility Initiative&lt;/a&gt;, in particular their standards for web content (WCAG), web apps (ARIA), and authoring tools (ATAG). There has been particular interest recently in addressing the authoring part of the equation.&lt;/p&gt;
&lt;p&gt;That’s because a crucial aspect of the accessibility of notebooks lies in the content itself. It is imperative that Jupyter front ends actively support notebook authors in creating accessible content. This involves, for instance, prompting authors to include descriptive alt text for images, and issuing warnings when heading ranks are skipped. Skipping heading ranks can lead to difficulties in navigating the document outline, especially for users relying on screen readers. Such warnings could be brought through the language server protocol for markdown cells for example.&lt;/p&gt;
&lt;p&gt;If your organization is interested in supporting such accessibility improvements, please reach out to the &lt;a href="https://github.com/jupyter/accessibility"&gt;Jupyter accessibility team&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;/h2&gt;
&lt;p&gt;Please try out the latest pre-release of JupyterLab 4.1. Try opening, editing, and saving a notebook without using your mouse or trackpad. Be aware that there are still areas of the UI as well as extensions that need fixing for mouseless use. We are keeping track of accessibility issues in a few places: &lt;a href="https://github.com/jupyterlab/jupyterlab/labels/tag%3AAccessibility"&gt;JupyterLab GitHub accessibility label&lt;/a&gt;, &lt;a href="https://github.com/jupyter/notebook/issues?q=is%3Aopen+is%3Aissue+label%3Atag%3AAccessibility"&gt;Notebook 7 GitHub accessibility label&lt;/a&gt;, JupyterLab issue &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/9399"&gt;#9399&lt;/a&gt;. As you will find at those links, there’s still loads more to do. If you can help with either fixing those things or identifying other issues, please come help us, your contribution is valuable and you are welcome!&lt;/p&gt;
&lt;h2 id="about-the-authors"&gt;About the Authors&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/NicolasBrichet_"&gt;Nicolas Brichet&lt;/a&gt; is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;, and a JupyterLab core team member. Prior to this work on keyboard navigation, Nicolas contributed to improving the accessibility of Jupyter by addressing the issues detected by the Axe accessibility testing engine.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/gabalafou"&gt;Gabriel Fouasnon&lt;/a&gt; is a frontend developer at &lt;a href="https://twitter.com/quansightai"&gt;Quansight&lt;/a&gt;, a member of the Jupyter Software Steering Council representing the accessibility subproject, and a JupyterLab core team member.&lt;/p&gt;
&lt;h2 id="acknowledgments"&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;This work on improving the keyboard navigation of JupyterLab was started with an audit of the keyboard navigation of the Jupyter Notebook v7 by Isabela Presedo-Floyd.&lt;/p&gt;
&lt;p&gt;The work of Gabriel Fouasnon and Isabela Presedo-Floyd at Quansight on Jupyter accessibility was funded by the Chan Zuckerberg Initiative, through the “&lt;a href="https://chanzuckerberg.com/eoss/"&gt;Essential Open Source Software for Science&lt;/a&gt;” (EOSS) grant program.&lt;/p&gt;
&lt;p&gt;The work by Nicolas Brichet at QuantStack on the accessibility of Jupyter was funded by &lt;a href="https://www.insee.fr/"&gt;INSEE&lt;/a&gt;, the French National Institute of Statistics and Economic Studies.&lt;/p&gt;
&lt;p&gt;We also want to acknowledge all of the members of the Jupyter Accessibility Council: Tania Allard, Alex Bozarth, Frédéric Collonval, Martha Cryan, Afshin T. Darian, R Ely, Tony Fast, Gabriel Fouasnon, Michał Krassowski, and Isabela Presedo-Floyd, without whom this work would not be possible.&lt;/p&gt;
</content><category term="accessibility"/><category term="JupyterLab"/></entry><entry><title>A theme editor for JupyterLab</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/" rel="alternate"/><published>2023-06-19T08:11:00+00:00</published><updated>2023-06-19T08:11:00+00:00</updated><author><name>Florence Haudin</name></author><id>tag:jasongrout.github.io,2023-06-19:/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/</id><summary type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as notebooks, text editors…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as &lt;a href="https://jupyterlab.readthedocs.io/en/stable/user/notebook.html#notebook"&gt;notebooks&lt;/a&gt;, text editors, consoles, and terminals. Like many similar tools, it allows users to choose from light or dark color themes. However, users may want to further adjust the looks of the interface, just for fun or for specific personal preferences or needs.&lt;/p&gt;
&lt;p&gt;The ability to fine-tune contrast, color palettes, and fonts can be very useful for accessibility, an essential requirement for software to be usable by the whole community. There is an ongoing &lt;a href="https://jupyter-accessibility.readthedocs.io/"&gt;project-wide effort&lt;/a&gt; to improve accessibility in Jupyter. Choices of color palettes impact color-blind users while font choices can have a significant impact on people affected by dyslexia, or who suffer from migraines.&lt;/p&gt;
&lt;p&gt;This diversity of requirements and preferences shows that end users should have the means to adjust parameters and tweak existing themes. Subtle differences can improve comfort significantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JupyterLab themes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Theming in JupyterLab is enabled by the fact that most of the layout and colors of the UI are defined by a set of CSS variables. There is no need for a complete stylesheet: one can set values for the base parameters from which the appearance of the user interface is derived. This system ensures a consistent look and feel throughout the application.&lt;/p&gt;
&lt;p&gt;Custom themes provide a set of values for the base CSS variables and package the resulting CSS file in JupyterLab extensions. &lt;a href="https://github.com/search?q=jupyter+lab+theme"&gt;A search on GitHub&lt;/a&gt; returns 29 repositories defining such custom themes for JupyterLab.&lt;/p&gt;
&lt;p&gt;Creating a theme requires implementing values for &lt;em&gt;dozens&lt;/em&gt; of base CSS variables. This is often achieved by theme authors tweaking the values given in the default dark or light themes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generating a consistent palette, testing, and packaging the outcome in the form of a JupyterLab extension requires development skills and is neither direct nor trivial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;A new tool for authoring JupyterLab themes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To lower the bar for customizing JupyterLab we created a new tool providing a simple interface for tuning the JupyterLab appearance interactively, allowing theme authors to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use the selected colors in their application and “pin down” the result in the configuration,&lt;/li&gt;
&lt;li&gt;export the outcome in a form amenable to packaging into a new JupyterLab theme.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The theme editor extension is a theme prototyping tool, a sandbox to test changes in colors, font family and font size, and a configuration tool for end users. It displays a reduced set of parameters one can play with, allowing users to select:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;colors through color pickers,&lt;/li&gt;
&lt;li&gt;numerical values from sliders (for font size, border radius and width),&lt;/li&gt;
&lt;li&gt;font families from a predefined dropdown list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A screenshot of JupyterLab with the theme editor panel is displayed in Figure 1, with a notebook opened in light theme. Figure 2 shows different screenshots with different custom themes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screenshot of JupyterLab interface with the theme editor on the left panel and a notebook opened. The theme chosen is the light default one." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/001-0_vj7tjopuS2GvZg3N.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 1: The theme editor extension in JupyterLab.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="4 screenshots of the JupyterLab interface are shown to illustrate the kinds of themes that can be generated by the theme editor, with gray, blue, orange and pink tones and different font families." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/002-0_5OLBiA8tmTbD1Nan.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 2: Examples of dynamically editing the color and fonts scheme of JupyterLab with the theme editor.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Color palettes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For all the colors (accent, brand, border, error, info, layout, success, warn), the same logic is applied. The user picks a base color and a palette is automatically calculated using Microsoft’s &lt;a href="https://www.fast.design/docs/api/fast-colors.colorpalette"&gt;fast-colors&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;Let’s take the specific example of the layout colors impacting the background of most elements of the interface. They are defined using &lt;em&gt;— jp-layout-colori&lt;/em&gt; CSS variables from white to light grays (for the light theme).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-200&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-400&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-layout-color4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-md-grey-600&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The number of colors in the palette fits the number of colors in the CSS set of variables (here the palette contains 5 colors). There are different ways to define colors (hexadecimal, RGBA, HSLA). The latter format has the advantage of giving direct access to luminance &lt;em&gt;L&lt;/em&gt;, a measure of the lightness of a given color.&lt;/p&gt;
&lt;p&gt;An inverse layout palette is computed for use with most text elements. The luminance of the inverse layout color &lt;em&gt;L’&lt;/em&gt; is calculated as &lt;em&gt;1-L&lt;/em&gt; plus a correction depending on how &lt;em&gt;1-L&lt;/em&gt; is close to 0.5. There isn’t a general CSS rule in JupyterLab concerning elements on top of a background but some PRs were proposed to reinforce the coupling between inverse layout elements and layout backgrounds with the same color index. The contrast still needs to be improved though, by using other palettes or better corrections when defining the inverse layout colors.&lt;/p&gt;
&lt;p&gt;The full process just described from picking a layout color to resulting palettes and the corresponding interface is illustrated in Figure 3.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The figure is divided into 3 parts. Part 1 is a screenshot with color picker with a green color selected. Part 2 is a capture showing the resulting palette calculated from this color choice. Finally, part 3 is a screenshot of JupyterLab interface with the different green tones calculated from the base color that has been picked." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/a-theme-editor-for-jupyterlab/images/003-0_vD9AcycD3qSCAJgu.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 3: From a color to the layout, and inverse layout palettes with the resulting interface.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Font size and font family&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As mentioned previously, font size and font family can be tuned too. Let’s take the example of the &lt;em&gt;— jp-ui-font-size[i]&lt;/em&gt; CSS variables defining the text size of most of the text elements in the interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-scale-factor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;8333em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;13px&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;/* Base font size */&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jp-ui-font-size3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;44em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A slider lets the user control the base font size associated with &lt;em&gt;— jp-ui-font-size1&lt;/em&gt;. The other font sizes are then calculated by applying a scale factor to the base font.&lt;/p&gt;
&lt;p&gt;Concerning font families, a non-exhaustive list is proposed (i.e., default browser fonts or Google fonts). It includes both standard accessible fonts (Arial, Courier, Helvetica, Sans Serif, system-ui, Times New Roman, Verdana) and less usual ones to create artistic or special interfaces with cursive fonts ( for instance Dancing Script or Single Day). Only 2 fonts are proposed for code: Space Mono and monospace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storing the new theme&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once satisfied with a new theme, users can export it using a button at the top of the theme editor panel. This creates a &lt;em&gt;variable.css&lt;/em&gt; file that can be packaged in a theme extension (see the &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts/"&gt;extension template&lt;/a&gt;). The new parameters are also synchronously saved in the settings editor and can be restored for the next JupyterLab opening if the boolean &lt;em&gt;useSettings&lt;/em&gt; is set to true. If not, the interface will look like in Figure 1: with light theme default CSS values. Resetting &lt;em&gt;useSettings&lt;/em&gt; to true will restore the formerly tuned parameters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Future development&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The look and feel of the theme editor UI still needs to be improved. We will work on unifying the styling of all interfaces making use of &lt;a href="https://github.com/rjsf-team/react-jsonschema-form"&gt;react-jsonschema-form&lt;/a&gt;. It is already used for the JupyterLab settings editor, the notebook metadata editor of JupyterLab 4.0, and several extensions.&lt;/p&gt;
&lt;p&gt;Moreover, the current implementation is bound to fast-colors palettes and we may want to use a different approach, like decoupling the layout and inverse layout palettes to reach better contrasts or give more freedom in the color choices.&lt;/p&gt;
&lt;p&gt;Any help (e.g., filling issues for bugs or enhancement requests, opening pull requests) to improve &lt;a href="https://github.com/jupyterlab-contrib/jupyterlab-theme-editor"&gt;the extension&lt;/a&gt; is welcome.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can install the &lt;a href="https://pypi.org/project/jupyter-theme-editor"&gt;PyPI package&lt;/a&gt; by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jupyter_theme_editor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also try the theme editor on &lt;a href="https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-theme-editor/main?urlpath=lab"&gt;Binder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aknowledgement&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a href="https://twitter.com/ihuicatls"&gt;Isabel Paredes&lt;/a&gt; for drawing the palette icon!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;About the authors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Florence Haudin is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;. She contributes to JupyterLab and to ipyleaflet.&lt;/p&gt;
&lt;p&gt;Frédéric Collonval supervised this work as a technical director at QuantStack. He is a member of the JupyterLab core team and authored several JupyterLab extensions.&lt;/p&gt;
</content><category term="accessibility"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>JupyterLab 4.0 is Here</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/jupyterlab-4-0-is-here/" rel="alternate"/><published>2023-06-05T20:04:00+00:00</published><updated>2023-06-05T21:25:00+00:00</updated><author><name>Jason Weill</name></author><id>tag:jasongrout.github.io,2023-06-05:/medium-archive/pelican/posts/2023/jupyterlab-4-0-is-here/</id><summary type="html">&lt;p&gt;The Jupyter contributor community is proud to announce JupyterLab 4.0, the next major release of our full-featured development environment…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The Jupyter contributor community is proud to announce JupyterLab 4.0, the next major release of our full-featured development environment. The package is now available on PyPI and conda-forge. You can upgrade by running &lt;code&gt;pip install --upgrade jupyterlab&lt;/code&gt; or &lt;code&gt;conda install -c conda-forge jupyterlab&lt;/code&gt;. We have updated our &lt;a href="https://jupyterlab.readthedocs.io/en/latest/index.html"&gt;JupyterLab Documentation&lt;/a&gt; for this new version.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/jupyterlab-4-0-is-here/images/001-1_Y3aNGoRED2q0pWjlHmr9wg.webp" alt="A screen shot of JupyterLab 4, showing many of its new and improved features." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Here are some of the major new features in JupyterLab 4.0, excerpted from our &lt;a href="https://jupyterlab.readthedocs.io/en/latest/getting_started/changelog.html"&gt;changelog&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JupyterLab is now faster&lt;/strong&gt;, thanks to improvements such as CSS rules optimization, CodeMirror 6, MathJax 3, and notebook windowing. By rendering only the parts of a notebook that fit in the web browser viewport, JupyterLab 4 is much more efficient than JupyterLab 3 was when working with large notebooks.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/jupyterlab-4-0-is-here/images/002-1_382cHBlUd2-kgWzpci8OOw.mp4" alt="A side-by-side animated comparison of performance between JupyterLab 3 and 4." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;There are additional performance improvements available via opt-in settings. You can help test them out by enabling them in your settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster tab-switching on Chromium browsers:&lt;br&gt;
“Settings” → “JupyterLab Shell” → switch “Hidden mode” to “contentVisibility”&lt;/li&gt;
&lt;li&gt;Better performance with long notebooks:&lt;br&gt;
“Settings” → “Notebook” → switch “Windowing mode” to “full”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;An upgraded text editor.&lt;/strong&gt; CodeMirror, the text editor used for cells and file editors, has been updated to &lt;a href="https://codemirror.net/"&gt;CodeMirror 6&lt;/a&gt;. This brings important accessibility and performance improvements as well as better customization capabilities. We have also improved the editor settings to be more powerful and more flexible.&lt;/p&gt;
&lt;p&gt;We have moved &lt;strong&gt;real-time collaboration&lt;/strong&gt; (RTC) into a separate package, &lt;a href="https://github.com/jupyterlab/jupyter_collaboration"&gt;jupyter_collaboration&lt;/a&gt;, version 1.0.0 of which is &lt;a href="https://github.com/jupyterlab/jupyter_collaboration/releases/tag/v1.0.0"&gt;now available&lt;/a&gt;. Highlights include support for displaying multiple cursors and selections, and support for registration of new shared model types. If you’d like to use RTC in JupyterLab 4, please install the jupyter_collaboration package.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/jupyterlab-4-0-is-here/images/003-1_DL6GE3nEYT9SGn7oDGnydQ.mp4" alt="A side-by-side animated view of two users collaborating in JupyterLab 4 using the jupyter_collaboration package for real-time collaboration (RTC)" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A new extension manager.&lt;/strong&gt; In JupyterLab 4, the Extension Manager includes both prebuilt Python extensions and extensions from &lt;a href="https://pypi.org/search/?c=Framework+%3A%3A+Jupyter+%3A%3A+JupyterLab"&gt;pypi.org&lt;/a&gt;. Installing from PyPI removes the need to build when installing an extension using the Extension Manager. Developers can provide an alternative package repository to display their own set of extensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improved document search.&lt;/strong&gt; The Search and Replace functionality has been improved with new features when searching in a notebook, including highlighting matches in rendered markdown cells, searching in the current selection, multi-line search, replacement using regular expression capture-group references, and replacement while preserving case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UI improvements,&lt;/strong&gt; including a reworked running kernels section, an “Add a new cell” button at the bottom of a notebook, a dialog to display keyboard shortcuts as in the Classic Notebook (use Ctrl + Shift + H), and displaying the first line of collapsed cell inputs and outputs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accessibility improvements,&lt;/strong&gt; including improved focus and keyboard navigation in the file browser, more ARIA roles and labels for UI elements, and the main menu collapsing to a hamburger menu if there is not enough space to display all items.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Development tool updates&lt;/strong&gt; to TypeScript v5, Yarn v3, React v18, and Lumino v2. We recommend using Node.js v18 or newer, to ensure that you’re using a version that will continue to receive updates through at least the end of 2023.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bug fixes.&lt;/strong&gt; More than 100 bugs have been addressed and resolved, enhancing JupyterLab’s stability and performance. You can find a full list of bug fixes &lt;a href="https://github.com/jupyterlab/jupyterlab/releases/tag/v4.0.0rc0"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition, a few features in JupyterLab 4 have also been backported to JupyterLab 3.6, but if you’re upgrading from 3.5 or earlier, they will also be new to you:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;dual-mode settings editor&lt;/strong&gt; now offers a JSON editor and a new, user-friendly interactive mode, built with React JSON Schema. This intuitive interface makes customization accessible to users of all skill levels.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;cell toolbar&lt;/strong&gt;, a graphical interface for quick cell manipulation (copy, move, insert, delete), provides an alternative to hotkeys or dropdown menu editing. The cell toolbar appears in the current cell as long as there is room for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notifications&lt;/strong&gt; come from extensions and from JupyterLab itself, for real-time announcements, alerts, and updates. This enhances JupyterLab’s responsiveness and provides a way to stay informed about ongoing processes. Announcements from the JupyterLab team, including new version notifications, can be disabled; see the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/user/announcements.html"&gt;documentation&lt;/a&gt; for instructions.&lt;/p&gt;
&lt;p&gt;Thank you to all the users, contributors, extension developers, and community members who helped make this major new release possible. We encourage everyone to try this new version out. If you notice anything unexpected, please &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/new/choose"&gt;file an issue on GitHub&lt;/a&gt; or ask us about it on &lt;a href="https://discourse.jupyter.org/"&gt;Discourse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Author’s note: This article is published on behalf of Project Jupyter. I appreciate the contributions of the JupyterLab developers who contributed to it.&lt;/em&gt;&lt;/p&gt;
</content><category term="JupyterLab"/><category term="releases"/></entry></feed>