From bd3d75b2fe79941e924b234910da0de50b975ae4 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:08:12 +0200 Subject: [PATCH 01/31] Finish up first draft --- apps/labs/posts/jupyter-everywhere.md | 229 ++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 apps/labs/posts/jupyter-everywhere.md diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md new file mode 100644 index 000000000..b2b0f0c4c --- /dev/null +++ b/apps/labs/posts/jupyter-everywhere.md @@ -0,0 +1,229 @@ +--- +title: 'Jupyter Everywhere: empowering interactive computing for K-12 education' # not final +published: November 01, 2025 +authors: [agriya-khetarpal] # ?[agriya-khetarpal, peyton-murray, michał-krassowski] how to add multiple authors? +description: 'The story of how Jupyter Everywhere is transforming K-12 education through interactive computing' +# "Jupyter" used to be a category for older blogs; it no longer is. "Interactive computing" could +# subsume it as well, if not "Jupyter". ig "Developer workflows" is not quite right... so for now: +category: ["OSS Experience"] +featuredImage: + src: /posts/jupyter-everywhere/placeholder.png + alt: placeholder +hero: + # JE octopus logo here + imageSrc: placeholder + imageAlt: placeholder +--- + +# The Jupyter Everywhere story: empowering interactive computing for K-12 education + + +Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), an innovative notebooks-based end-to-end application for K-12 education for high school students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). + +Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – cutting-edge technologies that enable running Jupyter notebooks entirely in a web browser. Particularly, we harness [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack for executing data science and statistical computing code in a browser environment without the need to provision any server-side dependencies or deployments. + +We'll discuss the challenges we faced, the features we implemented, and the lessons we learned while developing Jupyter Everywhere. I hope that this post will inspire educators, developers, and institutions to explore the potential of interactive computing in education, and to contribute to the open source ecosystems that make it all possible and worthwhile. + +## Introduction + +Jupyter notebooks have, since long, revolutionised the way we teach and learn programming, data science, and computational thinking. Their interactive nature allows users to experiment with code, visualise data, and document their thought processes in a single, top-down document format. However, the practice of setting up and maintaining Jupyter environments has proven to be a daunting task for educators, especially in K-12 settings where resources and technical expertise are often limited and not cut out for such endeavours. + +## Navigating the complexities of the Jupyter user interface + +Jupyter provides two traditional user interfaces: Jupyter Notebook, and JupyterLab, each with its own strengths and weaknesses. Jupyter Notebook offers a simple and straightforward interface, ideal for beginners and quick prototyping. On the other hand, JupyterLab provides a more powerful and flexible environment, catering to advanced users who require features like multi-document editing, integrated terminals, and extensibility through plugins. + +However, both interfaces are designed with a certain level of technical proficiency in mind, especially for newcomers to programming and interactive computing. There are myriads of features and options that can overwhelm many a novice, leading to confusion and frustration. For K-12 educators and students, who may not have prior experience with Jupyter or programming in general, the learning curve associated with these interfaces is steeper than what is anticipated by an educational setting. + + + +This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment (IDE) tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: octopus mascot) yet powerful enough to run anything a student might want to throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! + +## Administrative and technical challenges in K-12 settings and high school districts + +One of the primary challenges in high school districts is the lack of technical infrastructure and support for setting up and maintaining Jupyter environments, especially when dealing with a large number of students and educators. Many schools may not have dedicated IT staff or resources to manage JupyterHub installations, leading to potential downtime and an inconsistent user experience. A lot of this may be outsourced to third-party vendors who are experienced in setting up learning management systems (LMS) and JupyterHub instances, but this can be costly and may not always align with the specific needs of the school or district. +Additionally, ensuring that every student has access to the necessary software and libraries can be a logistical nightmare, especially when dealing with the fact that students are assigned a common set of hardware and needs to be configured in exactly the same way. + +To think about JupyterHub in such settings is a no-go: it invites further complexity, requiring user authentication, server management, and resource allocation. + +We attribute three key challenges that Jupyter Everywhere aims to address: +- Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. +- Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. +- Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. +- The lack of a database to store user data and notebooks grouped by accounts, such as by OAuth providers like Google or Microsoft. This is a limitation that needs to be circumvented by using alternative methods of notebook sharing and distribution, which we discuss later in this post. + +## The story of Jupyter Everywhere + +Here, we start describing our journey of building the application from the ground up, starting as a no-frills prototype to a full-fledged JupyterLite extension. + + + +### Customising JupyterLite, and key features of Jupyter Everywhere + +Jupyter Everywhere, as we mentioned earlier, is built on top of JupyterLite. JupyterLite is a distribution of JupyterLab that removes Jupyter's server-side components with standards for in-browser communication with language kernels either in JavaScript or compiled to WebAssembly (WASM), shims for server-side Jupyter APIs, and in-browser file systems for storing user content and settings. + +Initially, we started developing the application as a JupyterLab extension, as opposed to a JupyterLite extension, in order to prototype the functionality we wanted to build at first: interacting with the file system and being able to download notebooks in various formats. This was because we wanted to get proofs-of-concept working as quickly as possible, and JupyterLab provided a more familiar development environment, eventually changing to JupyterLite once we had a better idea of the workflow we wanted to implement. + +### Facilitating notebook sharing and distribution + +Jupyter Everywhere aims to simplify the process of sharing and distributing notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. For this, our collaborators, CourseKata developed a sharing service that allows authentication via JWT (JSON Web Tokens). This sharing service allows authentication by virtue of various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores the notebook as IPYNB/JSON, with the sharing-related metadata, embedded as JSON fields in the "metadata" field of the notebook. + +The sharing service provides two key features: +- Uploading notebooks to a server for storage and sharing +- Generating sharing IDs based on a hash of the current session and the notebook state, and readable IDs as a mnemonic for sharing purposes via a database of aquatic animal names and adjectives to make it more fun and engaging for students. + + + +#### Sharing notebooks via view-only links + +As a result of the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others, allowing them to view the notebook in a read-only mode without the ability to edit or modify its contents. + +Initially, this was implemented using a dialogue that generated a sharing link with a password. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were being deemed unnecessary as the view-only links inherently restrict editing capabilities and access. It would also reduce the cognitive load on users by eliminating the need to remember or manage additional credentials, when just a link would suffice for viewing purposes. + +Further on, later during the development process, we decided that it would be more user-friendly to generate friendlier URLs for sharing, as opposed to the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service also generated initially. To achieve this, the sharing service implemented a system that generates human-readable IDs based on a combination of aquatic animal names and adjectives, creating memorable and engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. + + + + +There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e, a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. + +Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click on the link to view the notebook in their web browser, without needing to install any additional software or create an account. + +When a user opens a shared notebook link, they are presented with a read-only view of the notebook, where they can navigate through the cells, view outputs, and interact with any visualisations or widgets embedded in the notebook. However, they cannot modify the notebook's contents or execute any cells. + +This was trickier to implement than we initially thought. At that time, JupyterLab did not allow starting a notebook without a kernel attached to it. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override the default behaviour of JupyterLab to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. + + + +#### Notebook downloads + +Another method for sharing notebooks is via exporting them out of Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which requires adding a button to the notebook panel toolbar. + +However, we wanted to go a step further and allow users to download notebooks in PDF format as well. This is currently not supported out-of-the-box in JupyterLite. JupyterLab relies on server-side components to generate PDFs from notebooks, through the use of `nbconvert` and LaTeX, which generate high-quality PDFs suitable for printing and sharing. However, since Jupyter Everywhere runs entirely in the browser without any server-side components, this approach is not feasible. + +Thus, we went forward with a custom approach for this, based on in-progress work in the JupyterLite community to add PDF export functionality using an in-browser PDF generation library called `jsPDF`. + + +`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without the need for server-side processing. We integrated `jsPDF` into Jupyter Everywhere by creating a custom download button that would convert the current notebook into a PDF document using `jsPDF`, and then trigger a download of the generated PDF file. However, this functionality is still a work in progress, and there is work to be done w.r.t. improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. + +#### Uploading notebooks to Jupyter Everywhere + +Since downloading notebooks is only half the story, you might ask – how does one upload notebooks, that might have been created elsewhere (whether in Jupyter Notebook, JupyterLab, or another JupyterLite instance, or Jupyter Everywhere itself), into Jupyter Everywhere? + +Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed as the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means that there is no visible file browser or file management interface in Jupyter Everywhere, in favour of not adding unnecessary complexity. + + +This means that users need to upload notebooks manually into a JupyterLite instance, outside and prior to the Jupyter Everywhere session being instantiated. The main entry point for this is the Jupyter Everywhere landing page, where we've added an "Upload a Notebook" button that allows users to select a local IPyNB file from their computer, and upload it into the JupyterLite file system. + +Here is a quick walkthrough of how it works behind the scenes. + + +When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use a feature in modern browsers called the Web Storage API to store the contents of the uploaded notebook to a `localStorage` without interacting with the sharing service without the user's explicit consent. + +We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup, and the notebook is read from the `localStorage` and written into the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from the `localStorage` to free up space, and no race conditions occur as the notebook is only read once during startup. + +### Working with data files + +#### The Files widget + +When working with Jupyter notebooks, especially in data science and education contexts, it is common to work with data files such as CSV/TSV files, or images in various formats, whether local or remote. In a traditional Jupyter environment, these files can be easily uploaded to the server-side file system and accessed from within the notebook. + +When building Jupyter Everywhere, we wanted to ensure that users could easily work with data files as well. Now, remember that we disabled the file browser drawer in Jupyter Everywhere to simplify the user interface, rendering it invisible. This meant that we had to find alternative ways for users to upload and access data files within their notebooks. + +To do this, we implemented yet another plugin to re-implement browsing files, which we call the "Files" widget, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. + + + +The files are displayed as part of a grid, akin to that of a file browser on phones and desktop computers that implement viewing a file, displayed as a tile consisting of a placeholder and an associated filename. + +While we started with bare-bones functionality, we soon realised that users would benefit from more features, such as the ability to delete the uploaded files, and download them back to their local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. This implied adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. + +The Files widget is accessible via a sidebar tab in Jupyter Everywhere, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files being stored in the same in-browser file system as the notebooks themselves, through the use of JupyterLite's `ContentsManager` API that abstracts away file system operations regardless of the underlying storage mechanism. + +#### The quest for being able to render uploaded files within the notebook interface + +However, as seamlessly as we expected for it to work, we ran into a quirk: when uploading certain file types, such as images in PNG or JPEG format, the files would not render correctly when accessed from within the notebook in Markdown cells. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for certain file formats. To address this, we had to implement custom logic to ensure that the correct MIME types were set when uploading and accessing these files, ensuring that they rendered correctly within the notebook interface. + +This took a few steps. First, we needed to investigate how JupyterLite handled MIME types for uploaded files, which we realised was done via the JupyterLab implementation via the `RenderMimeRegistry.UrlResolver` class. This class is not designed to handle custom resolution logic for files stored in the in-browser file system, so we had to extend it to add our own custom logic. This meant overriding the class to make it a "factory". In Jupyter, a "factory" is a design pattern that allows for the creation of objects without specifying the exact class of object that will be created. By making the `UrlResolver` a factory, we could inject our own custom logic for resolving URLs for files stored in the in-browser file system. + +Once we were able to allow swaping the URL resolver with out own in JupyterLab, and the JupyterLab version that JupyterLite is based on was updated to support this, this was ported to JupyterLite pretty easily – the `resolveUrl` function for JupyterLite was updated to base64-encode the data for popular image file formats, allowing them to be rendered correctly within the notebook interface. + + + +#### Working with remote files within the notebook interface + +Another common use case when working with Jupyter notebooks is the ability to access remote files, such as datasets hosted on public servers or cloud storage services. In a traditional Jupyter environment, users can easily download remote files using networking libraries like `requests` or `urllib`, or more advanced asynchronous counterparts like `aiohttp`, and then read them into their notebooks. + +However, notebooks in JupyterLite rely on Pyodide and xeus-r to provide programmatic interfaces akin to the Python and R programming languages respectively. There are a few differences between these in-browser runtime environments and traditional Jupyter kernels running on a server. + + + +For instance, Pyodide provides a built-in `pyodide.http` module that allows users to fetch remote files using the browser's native `fetch` API, which is asynchronous and non-blocking. This means that users can download remote files and read them into their notebooks without blocking the main thread of execution. + + + +For xeus-r and R, networking support was added in later versions. + + +For the Pyodide kernel, we implemented a plugin in Jupyter Everywhere to call `pyodide_http.patch_all()` silently underneath when the kernel first starts up. For students and educators, this means that they can use familiar public APIs from scientific Python libraries, like the `pandas.read_csv()` function to read remote CSV files directly into their notebooks without needing to know the underlying implementation details. + +### Run buttons next to code cells + +Popularised by platforms like [Observable](https://observablehq.com/), Google Colaboratory, Kaggle Notebooks, and Deepnote, "Run" buttons next to code cells have become a common feature in modern interactive computing environments. These buttons provide a convenient way for users to execute individual code cells with a single click, without needing to navigate to the toolbar or use keyboard shortcuts. + +However, Jupyter does not provide this functionality right away. . + + + +## A call to action for educators and institutions + + + +## What we learned from developing and deploying Jupyter Everywhere + +1. Test early, and test well. + + + + +2. User experience is paramount. Ask for feedback from educators and students, and iterate on the design based on their needs. Jupyter is fundamentally a tool for learning, but even it would be rendered useless if it were too difficult to use for a student or educator. Given that we deal with high school students, we have to be especially careful to ensure that the user experience is as intuitive and matches what someone with little to no programming experience, let along literate programming experience, would expect from a web application. + +3. Do not be afraid to pivot. We initially started with a set approach to building Jupyter Everywhere, but we were carefully discerning of our inner instrincts and the feedback we received from our collaborators, and were not afraid to change our approach when necessary. This flexibility allowed us to adapt to changing requirements and deliver a product that truly met the needs of our users. + + +## Some achievements and future directions + +Having a fully functional Jupyter environment that runs entirely in the browser is already a significant achievement. However, there is always room for improvement and expansion for future directions. Potential areas for future development could include: + + +- Per-cell stop buttons and full support for interrupting code execution for in-browser kernels +- Real-time collaboration in JupyterLite +- Improvements to the sharing service +- Improving PDF export functionality in JupyterLite using in-browser PDF generation libraries via JavaScript/WASM + +## Acknowledgements + +I, the author, am extremely grateful to have had this opportunity to work on Jupyter Everywhere. It is one of the most exciting end user applications of JupyterLite and WebAssembly that I have had the pleasure of contributing to, and considering that this was my first avenue into the world of Jupyter and programming in TypeScript, JavaScript, and React, I am pretty proud of what we have achieved as a team and how much I learned along the way, thanks to Michał Krassowski and Peyton Murray for their mentorship and guidance throughout the project. It feels amazing to see how far Jupyter has come in itself, and I believe there is no better way for me contribute to its growth than by building applications that align with my personal values of enhancing accessibility and driving education through technology. + +We lay down our gratitude to the following individuals and organisations for their invaluable contributions to the development and success of Jupyter Everywhere: + +- Jupyter ecosystem developers and maintainers +- JupyterLite developers and maintainers +- Educators and students who provided feedback during development, courtesy Skew The Script +- Gates Foundation https://www.gatesfoundation.org/about/committed-grants/2024/03/inv-067942 for their grant award to Skew The Script to support the development of Jupyter Everywhere +- The Scientific Python ecosystem +- The core developers and maintainers of Pyodide (how do I do this without sounding snobbish as I am a Pyodide maintainer myself?) +- CourseKata for their design and development of the sharing service, and infrastructure support for hosting Jupyter Everywhere on AWS +- The QuantStack team for their work on JupyterLite, the Xeus project, xeus-r https://blog.jupyter.org/r-in-the-browser-announcing-our-webassembly-distribution-9450e9539ed5 + + + +## References + +[1] JupyterLite: https://jupyterlite.readthedocs.io/en/latest/ + +[2] Pyodide: https://pyodide.org/en/stable/ + + + + + \ No newline at end of file From 05a0b72efe662843d77563325b34b8a265a14c42 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:31:16 +0200 Subject: [PATCH 02/31] Several corrections from UPGOER5 and Grammarly as applicable --- apps/labs/posts/jupyter-everywhere.md | 88 +++++++++++++-------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index b2b0f0c4c..012be503c 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -20,28 +20,28 @@ hero: Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), an innovative notebooks-based end-to-end application for K-12 education for high school students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). -Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – cutting-edge technologies that enable running Jupyter notebooks entirely in a web browser. Particularly, we harness [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack for executing data science and statistical computing code in a browser environment without the need to provision any server-side dependencies or deployments. +Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – cutting-edge technologies that enable running Jupyter notebooks entirely in a web browser. In particular, we use [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack, to execute data science and statistical computing code in a browser without provisioning any server-side dependencies or deployments. We'll discuss the challenges we faced, the features we implemented, and the lessons we learned while developing Jupyter Everywhere. I hope that this post will inspire educators, developers, and institutions to explore the potential of interactive computing in education, and to contribute to the open source ecosystems that make it all possible and worthwhile. ## Introduction -Jupyter notebooks have, since long, revolutionised the way we teach and learn programming, data science, and computational thinking. Their interactive nature allows users to experiment with code, visualise data, and document their thought processes in a single, top-down document format. However, the practice of setting up and maintaining Jupyter environments has proven to be a daunting task for educators, especially in K-12 settings where resources and technical expertise are often limited and not cut out for such endeavours. +Jupyter notebooks have, for a long time, revolutionised the way we teach and learn programming, data science, and computational thinking. Their interactive nature allows users to experiment with code, visualise data, and document their thought processes in a single, top-down document format. However, the practice of setting up and maintaining Jupyter environments has proven daunting for educators, especially in K-12 settings where resources and technical expertise are often limited and not well-suited to such endeavours. ## Navigating the complexities of the Jupyter user interface -Jupyter provides two traditional user interfaces: Jupyter Notebook, and JupyterLab, each with its own strengths and weaknesses. Jupyter Notebook offers a simple and straightforward interface, ideal for beginners and quick prototyping. On the other hand, JupyterLab provides a more powerful and flexible environment, catering to advanced users who require features like multi-document editing, integrated terminals, and extensibility through plugins. +Jupyter provides two traditional user interfaces: Jupyter Notebook and JupyterLab, each with its own strengths and weaknesses. Jupyter Notebook offers a straightforward interface, ideal for beginners and quick prototyping. On the other hand, JupyterLab provides a more powerful and flexible environment, catering to advanced users who require features such as multi-document editing, integrated terminals, and plugin extensibility. -However, both interfaces are designed with a certain level of technical proficiency in mind, especially for newcomers to programming and interactive computing. There are myriads of features and options that can overwhelm many a novice, leading to confusion and frustration. For K-12 educators and students, who may not have prior experience with Jupyter or programming in general, the learning curve associated with these interfaces is steeper than what is anticipated by an educational setting. +However, both interfaces are designed with a certain level of technical proficiency in mind, especially for newcomers to programming and interactive computing. Myriad features and options can overwhelm many a novice, leading to confusion and frustration. For K-12 educators and students who may not have prior experience with Jupyter or programming in general, the learning curve associated with these interfaces is steeper than anticipated in an educational setting. -This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment (IDE) tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: octopus mascot) yet powerful enough to run anything a student might want to throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! +This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment (IDE) tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! ## Administrative and technical challenges in K-12 settings and high school districts -One of the primary challenges in high school districts is the lack of technical infrastructure and support for setting up and maintaining Jupyter environments, especially when dealing with a large number of students and educators. Many schools may not have dedicated IT staff or resources to manage JupyterHub installations, leading to potential downtime and an inconsistent user experience. A lot of this may be outsourced to third-party vendors who are experienced in setting up learning management systems (LMS) and JupyterHub instances, but this can be costly and may not always align with the specific needs of the school or district. -Additionally, ensuring that every student has access to the necessary software and libraries can be a logistical nightmare, especially when dealing with the fact that students are assigned a common set of hardware and needs to be configured in exactly the same way. +One of the primary challenges in high school districts is the lack of technical infrastructure and support for setting up and maintaining Jupyter environments, especially when dealing with a large number of students and educators. Many schools may lack dedicated IT staff or resources to manage JupyterHub installations, leading to potential downtime and an inconsistent user experience. A lot of this may be outsourced to third-party vendors who are experienced in setting up learning management systems (LMS) and JupyterHub instances, but this can be costly and may not always align with the specific needs of the school or district. +Additionally, ensuring that every student has access to the necessary software and libraries can be a logistical nightmare, especially when students are assigned a standard set of hardware and must be configured identically. To think about JupyterHub in such settings is a no-go: it invites further complexity, requiring user authentication, server management, and resource allocation. @@ -49,11 +49,11 @@ We attribute three key challenges that Jupyter Everywhere aims to address: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. - Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. - Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. -- The lack of a database to store user data and notebooks grouped by accounts, such as by OAuth providers like Google or Microsoft. This is a limitation that needs to be circumvented by using alternative methods of notebook sharing and distribution, which we discuss later in this post. +- The lack of a database to store user data and notebooks grouped by accounts, such as by OAuth providers like Google or Microsoft. This limitation can be circumvented by using alternative methods for notebook sharing and distribution, which we discuss later in this post. ## The story of Jupyter Everywhere -Here, we start describing our journey of building the application from the ground up, starting as a no-frills prototype to a full-fledged JupyterLite extension. +Here, we start by describing our journey building the application from the ground up – from a no-frills prototype to a full-fledged JupyterLite extension. @@ -61,11 +61,11 @@ Here, we start describing our journey of building the application from the groun Jupyter Everywhere, as we mentioned earlier, is built on top of JupyterLite. JupyterLite is a distribution of JupyterLab that removes Jupyter's server-side components with standards for in-browser communication with language kernels either in JavaScript or compiled to WebAssembly (WASM), shims for server-side Jupyter APIs, and in-browser file systems for storing user content and settings. -Initially, we started developing the application as a JupyterLab extension, as opposed to a JupyterLite extension, in order to prototype the functionality we wanted to build at first: interacting with the file system and being able to download notebooks in various formats. This was because we wanted to get proofs-of-concept working as quickly as possible, and JupyterLab provided a more familiar development environment, eventually changing to JupyterLite once we had a better idea of the workflow we wanted to implement. +Initially, we started developing the application as a JupyterLab extension rather than a JupyterLite extension to prototype the functionality we wanted to build first: interacting with the file system and downloading notebooks in various formats. This was because we wanted to get proofs of concept up and running as quickly as possible. JupyterLab provided a more familiar development environment, and we eventually switched to JupyterLite once we had a clearer idea of the workflow we wanted to implement. ### Facilitating notebook sharing and distribution -Jupyter Everywhere aims to simplify the process of sharing and distributing notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. For this, our collaborators, CourseKata developed a sharing service that allows authentication via JWT (JSON Web Tokens). This sharing service allows authentication by virtue of various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores the notebook as IPYNB/JSON, with the sharing-related metadata, embedded as JSON fields in the "metadata" field of the notebook. +Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service allows authentication by virtue of various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as IPYNB/JSON, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. The sharing service provides two key features: - Uploading notebooks to a server for storage and sharing @@ -75,102 +75,102 @@ The sharing service provides two key features: #### Sharing notebooks via view-only links -As a result of the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others, allowing them to view the notebook in a read-only mode without the ability to edit or modify its contents. +As a result of the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others to view the notebook in read-only mode, without the ability to edit its contents. -Initially, this was implemented using a dialogue that generated a sharing link with a password. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were being deemed unnecessary as the view-only links inherently restrict editing capabilities and access. It would also reduce the cognitive load on users by eliminating the need to remember or manage additional credentials, when just a link would suffice for viewing purposes. +Initially, this was implemented using a dialogue that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. -Further on, later during the development process, we decided that it would be more user-friendly to generate friendlier URLs for sharing, as opposed to the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service also generated initially. To achieve this, the sharing service implemented a system that generates human-readable IDs based on a combination of aquatic animal names and adjectives, creating memorable and engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. +Later in the development process, we decided it would be more user-friendly to generate friendlier URLs for sharing, rather than the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service initially generated. To achieve this, the sharing service implemented a system that produces human-readable IDs from combinations of aquatic animal names and adjectives, creating memorable, engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e, a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. -Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click on the link to view the notebook in their web browser, without needing to install any additional software or create an account. +Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click the link to view the notebook in their web browser, without installing any additional software or creating an account. When a user opens a shared notebook link, they are presented with a read-only view of the notebook, where they can navigate through the cells, view outputs, and interact with any visualisations or widgets embedded in the notebook. However, they cannot modify the notebook's contents or execute any cells. -This was trickier to implement than we initially thought. At that time, JupyterLab did not allow starting a notebook without a kernel attached to it. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override the default behaviour of JupyterLab to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. +This was trickier to implement than we initially thought. At that time, JupyterLab did not allow starting a notebook without a kernel attached. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override JupyterLab's default behaviour to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. #### Notebook downloads -Another method for sharing notebooks is via exporting them out of Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which requires adding a button to the notebook panel toolbar. +Another method for sharing notebooks is to export them from Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which requires adding a button to the notebook panel toolbar. -However, we wanted to go a step further and allow users to download notebooks in PDF format as well. This is currently not supported out-of-the-box in JupyterLite. JupyterLab relies on server-side components to generate PDFs from notebooks, through the use of `nbconvert` and LaTeX, which generate high-quality PDFs suitable for printing and sharing. However, since Jupyter Everywhere runs entirely in the browser without any server-side components, this approach is not feasible. +However, we wanted to go a step further and allow users to download notebooks in PDF format as well. This is currently not supported out of the box in JupyterLite. JupyterLab relies on server-side components to generate PDFs from notebooks using `nbconvert` and LaTeX, creating high-quality PDFs suitable for printing and sharing. However, since Jupyter Everywhere runs entirely in the browser without any server-side components, this approach is not feasible. Thus, we went forward with a custom approach for this, based on in-progress work in the JupyterLite community to add PDF export functionality using an in-browser PDF generation library called `jsPDF`. -`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without the need for server-side processing. We integrated `jsPDF` into Jupyter Everywhere by creating a custom download button that would convert the current notebook into a PDF document using `jsPDF`, and then trigger a download of the generated PDF file. However, this functionality is still a work in progress, and there is work to be done w.r.t. improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. +`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without server-side processing. We integrated `jsPDF` into Jupyter Everywhere by creating a custom download button that converts the current notebook to a PDF and triggers its download. However, this functionality is still a work in progress. There is work to be done, viz., improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. #### Uploading notebooks to Jupyter Everywhere -Since downloading notebooks is only half the story, you might ask – how does one upload notebooks, that might have been created elsewhere (whether in Jupyter Notebook, JupyterLab, or another JupyterLite instance, or Jupyter Everywhere itself), into Jupyter Everywhere? +Since downloading notebooks is only half the story, you might ask – how does one upload notebooks that might have been created elsewhere (whether in Jupyter Notebook, JupyterLab, or another JupyterLite instance, or Jupyter Everywhere itself), into Jupyter Everywhere? -Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed as the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means that there is no visible file browser or file management interface in Jupyter Everywhere, in favour of not adding unnecessary complexity. +Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means there is no visible file browser or file management interface in Jupyter Everywhere, to avoid unnecessary complexity. -This means that users need to upload notebooks manually into a JupyterLite instance, outside and prior to the Jupyter Everywhere session being instantiated. The main entry point for this is the Jupyter Everywhere landing page, where we've added an "Upload a Notebook" button that allows users to select a local IPyNB file from their computer, and upload it into the JupyterLite file system. +Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. The main entry point for this is the Jupyter Everywhere landing page, where we've added an "Upload a Notebook" button that lets users select a local IPyNB file from their computer and upload it to the JupyterLite file system. Here is a quick walkthrough of how it works behind the scenes. -When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use a feature in modern browsers called the Web Storage API to store the contents of the uploaded notebook to a `localStorage` without interacting with the sharing service without the user's explicit consent. +When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use a feature in modern browsers, the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. -We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup, and the notebook is read from the `localStorage` and written into the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from the `localStorage` to free up space, and no race conditions occur as the notebook is only read once during startup. +We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup. The notebook is read from `localStorage` and written to the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from `localStorage` to free up space, and no race conditions occur because the notebook is read only once during startup. ### Working with data files #### The Files widget -When working with Jupyter notebooks, especially in data science and education contexts, it is common to work with data files such as CSV/TSV files, or images in various formats, whether local or remote. In a traditional Jupyter environment, these files can be easily uploaded to the server-side file system and accessed from within the notebook. +When working with Jupyter notebooks, especially in data science and education contexts, it is common to use data files in formats such as CSV/TSV and image files, whether local or remote. In a traditional Jupyter environment, these files can be easily uploaded to the server-side file system and accessed from within the notebook. When building Jupyter Everywhere, we wanted to ensure that users could easily work with data files as well. Now, remember that we disabled the file browser drawer in Jupyter Everywhere to simplify the user interface, rendering it invisible. This meant that we had to find alternative ways for users to upload and access data files within their notebooks. -To do this, we implemented yet another plugin to re-implement browsing files, which we call the "Files" widget, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. +To do this, we implemented yet another plugin to reimplement file browsing, which we call the "Files" widget, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. -The files are displayed as part of a grid, akin to that of a file browser on phones and desktop computers that implement viewing a file, displayed as a tile consisting of a placeholder and an associated filename. +The files are displayed in a grid, akin to a file browser on phones and desktop computers, with each file shown as a tile containing a placeholder and its associated filename. -While we started with bare-bones functionality, we soon realised that users would benefit from more features, such as the ability to delete the uploaded files, and download them back to their local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. This implied adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. +While we started with bare-bones functionality, we soon realised that users would benefit from more features, such as the ability to delete the uploaded files and download them back to their local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. This implied adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. -The Files widget is accessible via a sidebar tab in Jupyter Everywhere, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files being stored in the same in-browser file system as the notebooks themselves, through the use of JupyterLite's `ContentsManager` API that abstracts away file system operations regardless of the underlying storage mechanism. +The Files widget is accessible via a sidebar tab in Jupyter Everywhere, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files stored in the same in-browser file system as the notebooks themselves. It uses JupyterLite's `ContentsManager` API, which abstracts away file system operations regardless of the underlying storage mechanism. #### The quest for being able to render uploaded files within the notebook interface -However, as seamlessly as we expected for it to work, we ran into a quirk: when uploading certain file types, such as images in PNG or JPEG format, the files would not render correctly when accessed from within the notebook in Markdown cells. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for certain file formats. To address this, we had to implement custom logic to ensure that the correct MIME types were set when uploading and accessing these files, ensuring that they rendered correctly within the notebook interface. +However, as seamlessly as we expected it to work, we ran into a quirk: when uploading specific file types, such as images in PNG or JPEG format, the files would not render correctly when accessed in Markdown cells within the notebook. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for specific file formats. To address this, we implemented custom logic to set the correct MIME types when uploading and accessing these files, ensuring they rendered correctly in the notebook interface. -This took a few steps. First, we needed to investigate how JupyterLite handled MIME types for uploaded files, which we realised was done via the JupyterLab implementation via the `RenderMimeRegistry.UrlResolver` class. This class is not designed to handle custom resolution logic for files stored in the in-browser file system, so we had to extend it to add our own custom logic. This meant overriding the class to make it a "factory". In Jupyter, a "factory" is a design pattern that allows for the creation of objects without specifying the exact class of object that will be created. By making the `UrlResolver` a factory, we could inject our own custom logic for resolving URLs for files stored in the in-browser file system. +This took a few steps. First, we needed to investigate how JupyterLite handled MIME types for uploaded files, which we realised was done via the JupyterLab implementation via the `RenderMimeRegistry.UrlResolver` class. This class is not designed to handle custom resolution logic for files stored in the in-browser file system, so we had to extend it to add our own resolution logic. This meant overriding the class to make it a "factory". In Jupyter, a "factory" is a design pattern that allows the creation of objects without specifying the exact class of the object to be created. By making the `UrlResolver` a factory, we could inject our own custom logic for resolving URLs for files stored in the in-browser file system. -Once we were able to allow swaping the URL resolver with out own in JupyterLab, and the JupyterLab version that JupyterLite is based on was updated to support this, this was ported to JupyterLite pretty easily – the `resolveUrl` function for JupyterLite was updated to base64-encode the data for popular image file formats, allowing them to be rendered correctly within the notebook interface. +Once we were able to allow swapping the URL resolver with our own in JupyterLab, and the JupyterLab version that JupyterLite is based on was updated to support this, this was ported to JupyterLite pretty easily – the `resolveUrl` function for JupyterLite was updated to base64-encode the data for popular image file formats, allowing them to be rendered correctly within the notebook interface. #### Working with remote files within the notebook interface -Another common use case when working with Jupyter notebooks is the ability to access remote files, such as datasets hosted on public servers or cloud storage services. In a traditional Jupyter environment, users can easily download remote files using networking libraries like `requests` or `urllib`, or more advanced asynchronous counterparts like `aiohttp`, and then read them into their notebooks. +Another everyday use case when working with Jupyter notebooks is accessing remote files, such as datasets hosted on public servers or cloud storage services. In a traditional Jupyter environment, users can easily download remote files using networking libraries like `requests` or `urllib`, or more advanced asynchronous counterparts like `aiohttp`, and then read them into their notebooks. -However, notebooks in JupyterLite rely on Pyodide and xeus-r to provide programmatic interfaces akin to the Python and R programming languages respectively. There are a few differences between these in-browser runtime environments and traditional Jupyter kernels running on a server. +However, notebooks in JupyterLite rely on Pyodide and xeus-r to provide programmatic interfaces akin to those of Python and R, respectively. There are a few differences between these in-browser runtime environments and traditional Jupyter kernels running on a server. For instance, Pyodide provides a built-in `pyodide.http` module that allows users to fetch remote files using the browser's native `fetch` API, which is asynchronous and non-blocking. This means that users can download remote files and read them into their notebooks without blocking the main thread of execution. - + For xeus-r and R, networking support was added in later versions. -For the Pyodide kernel, we implemented a plugin in Jupyter Everywhere to call `pyodide_http.patch_all()` silently underneath when the kernel first starts up. For students and educators, this means that they can use familiar public APIs from scientific Python libraries, like the `pandas.read_csv()` function to read remote CSV files directly into their notebooks without needing to know the underlying implementation details. +For the Pyodide kernel, we implemented a Jupyter Everywhere plugin that calls `pyodide_http.patch_all()` silently when the kernel first starts up. For students and educators, this means they can use familiar public APIs from scientific Python and data analysis libraries, such as the `pandas.read_csv()` function, to read remote CSV files directly into their notebooks without needing to know the underlying implementation details. ### Run buttons next to code cells -Popularised by platforms like [Observable](https://observablehq.com/), Google Colaboratory, Kaggle Notebooks, and Deepnote, "Run" buttons next to code cells have become a common feature in modern interactive computing environments. These buttons provide a convenient way for users to execute individual code cells with a single click, without needing to navigate to the toolbar or use keyboard shortcuts. +Popularised by platforms such as [Observable](https://observablehq.com/), Google Colaboratory, Kaggle Notebooks, and Deepnote, "Run" buttons next to code cells are a common feature in modern interactive computing environments. These buttons provide a convenient way for users to execute individual code cells with a single click, without needing to navigate to the toolbar or use keyboard shortcuts. -However, Jupyter does not provide this functionality right away. . +However, Jupyter does not provide this functionality out of the box. . @@ -185,10 +185,10 @@ However, Jupyter does not provide this functionality right away. -2. User experience is paramount. Ask for feedback from educators and students, and iterate on the design based on their needs. Jupyter is fundamentally a tool for learning, but even it would be rendered useless if it were too difficult to use for a student or educator. Given that we deal with high school students, we have to be especially careful to ensure that the user experience is as intuitive and matches what someone with little to no programming experience, let along literate programming experience, would expect from a web application. +2. User experience is paramount. Ask educators and students for feedback and iterate on the design based on their needs. Jupyter is fundamentally a tool for learning, but even it would be rendered useless if it were too difficult for students or educators to use. Given that we deal with high school students, we have to be especially careful to ensure the user experience is as intuitive as possible and aligns with what someone with little to no programming experience, let alone literate programming experience, would expect from a web application. -3. Do not be afraid to pivot. We initially started with a set approach to building Jupyter Everywhere, but we were carefully discerning of our inner instrincts and the feedback we received from our collaborators, and were not afraid to change our approach when necessary. This flexibility allowed us to adapt to changing requirements and deliver a product that truly met the needs of our users. - +3. Do not be afraid to pivot. We initially started with a set approach to building Jupyter Everywhere. Still, we were carefully discerning of our inner instincts and the feedback we received from our collaborators, and were not afraid to change our approach when necessary. This flexibility enabled us to adapt to changing requirements and deliver a product that truly met our users' needs. + ## Some achievements and future directions @@ -202,7 +202,7 @@ Having a fully functional Jupyter environment that runs entirely in the browser ## Acknowledgements -I, the author, am extremely grateful to have had this opportunity to work on Jupyter Everywhere. It is one of the most exciting end user applications of JupyterLite and WebAssembly that I have had the pleasure of contributing to, and considering that this was my first avenue into the world of Jupyter and programming in TypeScript, JavaScript, and React, I am pretty proud of what we have achieved as a team and how much I learned along the way, thanks to Michał Krassowski and Peyton Murray for their mentorship and guidance throughout the project. It feels amazing to see how far Jupyter has come in itself, and I believe there is no better way for me contribute to its growth than by building applications that align with my personal values of enhancing accessibility and driving education through technology. +I, the author, am incredibly grateful to have had this opportunity to work on Jupyter Everywhere. It is one of the most exciting end user applications of JupyterLite and WebAssembly that I have had the pleasure of contributing to, and considering that this was my first avenue into the world of Jupyter and programming in TypeScript, JavaScript, and React, I am pretty proud of what we have achieved as a team and how much I learned along the way, thanks to Michał Krassowski and Peyton Murray for their mentorship and guidance throughout the project. It feels fantastic to see how far Jupyter has come. I believe there is no better way for me to contribute to its growth than by building applications that align with my personal values of enhancing accessibility and driving education through technology. We lay down our gratitude to the following individuals and organisations for their invaluable contributions to the development and success of Jupyter Everywhere: @@ -211,7 +211,7 @@ We lay down our gratitude to the following individuals and organisations for the - Educators and students who provided feedback during development, courtesy Skew The Script - Gates Foundation https://www.gatesfoundation.org/about/committed-grants/2024/03/inv-067942 for their grant award to Skew The Script to support the development of Jupyter Everywhere - The Scientific Python ecosystem -- The core developers and maintainers of Pyodide (how do I do this without sounding snobbish as I am a Pyodide maintainer myself?) +- The core developers and maintainers of Pyodide (how do I do this without sounding snobbish, as I am a Pyodide maintainer myself?) - CourseKata for their design and development of the sharing service, and infrastructure support for hosting Jupyter Everywhere on AWS - The QuantStack team for their work on JupyterLite, the Xeus project, xeus-r https://blog.jupyter.org/r-in-the-browser-announcing-our-webassembly-distribution-9450e9539ed5 @@ -226,4 +226,4 @@ We lay down our gratitude to the following individuals and organisations for the - \ No newline at end of file + From f22588902b39acc81e7ded56360d1de1ebb0a044 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:27:00 +0200 Subject: [PATCH 03/31] Clarify comment about multiple authors --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 012be503c..89e650453 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -1,7 +1,7 @@ --- title: 'Jupyter Everywhere: empowering interactive computing for K-12 education' # not final published: November 01, 2025 -authors: [agriya-khetarpal] # ?[agriya-khetarpal, peyton-murray, michał-krassowski] how to add multiple authors? +authors: [agriya-khetarpal] # ?[agriya-khetarpal, peyton-murray, michał-krassowski] how to add multiple authors as peyton and mike are at OpenTeams now? description: 'The story of how Jupyter Everywhere is transforming K-12 education through interactive computing' # "Jupyter" used to be a category for older blogs; it no longer is. "Interactive computing" could # subsume it as well, if not "Jupyter". ig "Developer workflows" is not quite right... so for now: From b2a6d3134ba29eb29cd82f65f6e32385e502ad2f Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:09:32 +0100 Subject: [PATCH 04/31] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com> --- apps/labs/posts/jupyter-everywhere.md | 43 +++++++++++++-------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 89e650453..9be7f343f 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -1,7 +1,7 @@ --- title: 'Jupyter Everywhere: empowering interactive computing for K-12 education' # not final published: November 01, 2025 -authors: [agriya-khetarpal] # ?[agriya-khetarpal, peyton-murray, michał-krassowski] how to add multiple authors as peyton and mike are at OpenTeams now? +authors: [agriya-khetarpal] description: 'The story of how Jupyter Everywhere is transforming K-12 education through interactive computing' # "Jupyter" used to be a category for older blogs; it no longer is. "Interactive computing" could # subsume it as well, if not "Jupyter". ig "Developer workflows" is not quite right... so for now: @@ -18,11 +18,11 @@ hero: # The Jupyter Everywhere story: empowering interactive computing for K-12 education -Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), an innovative notebooks-based end-to-end application for K-12 education for high school students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). +Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), a notebooks-based end-to-end application for high school (K-12) students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). -Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – cutting-edge technologies that enable running Jupyter notebooks entirely in a web browser. In particular, we use [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack, to execute data science and statistical computing code in a browser without provisioning any server-side dependencies or deployments. +Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – technologies that enable running Jupyter notebooks entirely in a web browser. In particular, we use [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack, to execute data science and statistical computing code in a browser without provisioning any server-side dependencies or deployments. -We'll discuss the challenges we faced, the features we implemented, and the lessons we learned while developing Jupyter Everywhere. I hope that this post will inspire educators, developers, and institutions to explore the potential of interactive computing in education, and to contribute to the open source ecosystems that make it all possible and worthwhile. +I'll discuss the challenges we faced, the features we implemented, and the lessons we learned while developing Jupyter Everywhere. I hope that this post will inspire educators, developers, and institutions to explore the potential of interactive computing in education, and to contribute to the open source ecosystems that make it all possible and worthwhile. ## Introduction @@ -36,7 +36,7 @@ However, both interfaces are designed with a certain level of technical proficie -This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment (IDE) tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! +This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! ## Administrative and technical challenges in K-12 settings and high school districts @@ -45,7 +45,7 @@ Additionally, ensuring that every student has access to the necessary software a To think about JupyterHub in such settings is a no-go: it invites further complexity, requiring user authentication, server management, and resource allocation. -We attribute three key challenges that Jupyter Everywhere aims to address: +We identify three key challenges that Jupyter Everywhere aims to address: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. - Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. - Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. @@ -59,13 +59,13 @@ Here, we start by describing our journey building the application from the groun ### Customising JupyterLite, and key features of Jupyter Everywhere -Jupyter Everywhere, as we mentioned earlier, is built on top of JupyterLite. JupyterLite is a distribution of JupyterLab that removes Jupyter's server-side components with standards for in-browser communication with language kernels either in JavaScript or compiled to WebAssembly (WASM), shims for server-side Jupyter APIs, and in-browser file systems for storing user content and settings. +Jupyter Everywhere, as we mentioned earlier, is built on top of JupyterLite. JupyterLite is a distribution of JupyterLab that replaces Jupyter's server-side components with standards for in-browser communication with language kernels either in JavaScript or compiled to WebAssembly (WASM), shims for server-side Jupyter APIs, and in-browser file systems for storing user content and settings. -Initially, we started developing the application as a JupyterLab extension rather than a JupyterLite extension to prototype the functionality we wanted to build first: interacting with the file system and downloading notebooks in various formats. This was because we wanted to get proofs of concept up and running as quickly as possible. JupyterLab provided a more familiar development environment, and we eventually switched to JupyterLite once we had a clearer idea of the workflow we wanted to implement. +Initially, we started developing the application as a JupyterLab extension to prototype the functionality we wanted to build first: interacting with the file system and downloading notebooks in various formats. This enabled quick creation of the proof of concept, without the need to recompile JupyterLite during the initial iterations. Once we had a clearer idea of the UI and user workflows that needed to be implemented, we switched to a JupyterLite application. ### Facilitating notebook sharing and distribution -Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service allows authentication by virtue of various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as IPYNB/JSON, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. +Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service allows authentication through various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as `.ipynb` files, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. The sharing service provides two key features: - Uploading notebooks to a server for storage and sharing @@ -75,7 +75,7 @@ The sharing service provides two key features: #### Sharing notebooks via view-only links -As a result of the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others to view the notebook in read-only mode, without the ability to edit its contents. +To integrate the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others to view the notebook in read-only mode, without the ability to edit its contents. Initially, this was implemented using a dialogue that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. @@ -90,20 +90,19 @@ Once the link is generated, users can share the link with others on any platform When a user opens a shared notebook link, they are presented with a read-only view of the notebook, where they can navigate through the cells, view outputs, and interact with any visualisations or widgets embedded in the notebook. However, they cannot modify the notebook's contents or execute any cells. -This was trickier to implement than we initially thought. At that time, JupyterLab did not allow starting a notebook without a kernel attached. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override JupyterLab's default behaviour to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. +This was trickier to implement than we initially thought. At that time, JupyterLab did not expose a command for opening a notebook without a kernel attached. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override JupyterLab's default behaviour to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. #### Notebook downloads -Another method for sharing notebooks is to export them from Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which requires adding a button to the notebook panel toolbar. +Another method for sharing notebooks is to export them from Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which required adding a button to the notebook panel toolbar. However, we wanted to go a step further and allow users to download notebooks in PDF format as well. This is currently not supported out of the box in JupyterLite. JupyterLab relies on server-side components to generate PDFs from notebooks using `nbconvert` and LaTeX, creating high-quality PDFs suitable for printing and sharing. However, since Jupyter Everywhere runs entirely in the browser without any server-side components, this approach is not feasible. -Thus, we went forward with a custom approach for this, based on in-progress work in the JupyterLite community to add PDF export functionality using an in-browser PDF generation library called `jsPDF`. - +Thus, we went forward with a custom approach for this, based on [in-progress work in the JupyterLite community](https://github.com/jupyterlite/jupyterlite/pull/1625) to add PDF export functionality using an in-browser PDF generation library called `jsPDF`. -`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without server-side processing. We integrated `jsPDF` into Jupyter Everywhere by creating a custom download button that converts the current notebook to a PDF and triggers its download. However, this functionality is still a work in progress. There is work to be done, viz., improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. +`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without server-side processing. We integrated `jsPDF` into Jupyter Everywhere via a custom download button that converts the current notebook to a PDF and triggers its download. However, this functionality is still a work in progress. There is work to be done: improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. #### Uploading notebooks to Jupyter Everywhere @@ -117,7 +116,7 @@ Hence, users need to upload notebooks manually to a JupyterLite instance before Here is a quick walkthrough of how it works behind the scenes. -When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use a feature in modern browsers, the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. +When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup. The notebook is read from `localStorage` and written to the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from `localStorage` to free up space, and no race conditions occur because the notebook is read only once during startup. @@ -125,19 +124,17 @@ We then redirect the user away from the Jupyter Everywhere landing page and to t #### The Files widget -When working with Jupyter notebooks, especially in data science and education contexts, it is common to use data files in formats such as CSV/TSV and image files, whether local or remote. In a traditional Jupyter environment, these files can be easily uploaded to the server-side file system and accessed from within the notebook. +When working with Jupyter notebooks, especially in data science and education contexts, access to tabular data files CSV/TSV and image files, whether local or remote, is taken for granted. In a traditional Jupyter environment, these files can be easily uploaded to the server's file system and accessed within the notebook. -When building Jupyter Everywhere, we wanted to ensure that users could easily work with data files as well. Now, remember that we disabled the file browser drawer in Jupyter Everywhere to simplify the user interface, rendering it invisible. This meant that we had to find alternative ways for users to upload and access data files within their notebooks. - -To do this, we implemented yet another plugin to reimplement file browsing, which we call the "Files" widget, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. +Jupyter Everywhere design called for a simplified interface, with the default file browser available in JupyterLite replaced by a dedicated Files tab. We implemented it in another plugin, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. The files are displayed in a grid, akin to a file browser on phones and desktop computers, with each file shown as a tile containing a placeholder and its associated filename. -While we started with bare-bones functionality, we soon realised that users would benefit from more features, such as the ability to delete the uploaded files and download them back to their local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. This implied adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. +After starting with bare-bones functionality, we added the ability to delete the uploaded files and download them back to local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. The design that we settled on was adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. -The Files widget is accessible via a sidebar tab in Jupyter Everywhere, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files stored in the same in-browser file system as the notebooks themselves. It uses JupyterLite's `ContentsManager` API, which abstracts away file system operations regardless of the underlying storage mechanism. +The Files widget is accessible via a sidebar tab, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files stored in the same in-browser file system as the notebooks themselves. It uses JupyterLite's `ContentsManager` API, which abstracts away file system operations regardless of the underlying storage mechanism. #### The quest for being able to render uploaded files within the notebook interface @@ -151,7 +148,7 @@ Once we were able to allow swapping the URL resolver with our own in JupyterLab, #### Working with remote files within the notebook interface -Another everyday use case when working with Jupyter notebooks is accessing remote files, such as datasets hosted on public servers or cloud storage services. In a traditional Jupyter environment, users can easily download remote files using networking libraries like `requests` or `urllib`, or more advanced asynchronous counterparts like `aiohttp`, and then read them into their notebooks. +Another common use case when working with Jupyter notebooks is accessing remote files, such as datasets hosted on public servers or cloud storage services. In a traditional Jupyter environment, users can easily download remote files using networking libraries like `requests` or `urllib`, or more advanced asynchronous counterparts like `aiohttp`, and then read them into their notebooks. However, notebooks in JupyterLite rely on Pyodide and xeus-r to provide programmatic interfaces akin to those of Python and R, respectively. There are a few differences between these in-browser runtime environments and traditional Jupyter kernels running on a server. From 4cc1862f609f9573d6833b1eb5110762bb02625c Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:12:51 +0100 Subject: [PATCH 05/31] Improve `UrlResolver` description stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com> --- apps/labs/posts/jupyter-everywhere.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 9be7f343f..b07638831 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -140,7 +140,13 @@ The Files widget is accessible via a sidebar tab, allowing users to easily switc However, as seamlessly as we expected it to work, we ran into a quirk: when uploading specific file types, such as images in PNG or JPEG format, the files would not render correctly when accessed in Markdown cells within the notebook. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for specific file formats. To address this, we implemented custom logic to set the correct MIME types when uploading and accessing these files, ensuring they rendered correctly in the notebook interface. -This took a few steps. First, we needed to investigate how JupyterLite handled MIME types for uploaded files, which we realised was done via the JupyterLab implementation via the `RenderMimeRegistry.UrlResolver` class. This class is not designed to handle custom resolution logic for files stored in the in-browser file system, so we had to extend it to add our own resolution logic. This meant overriding the class to make it a "factory". In Jupyter, a "factory" is a design pattern that allows the creation of objects without specifying the exact class of the object to be created. By making the `UrlResolver` a factory, we could inject our own custom logic for resolving URLs for files stored in the in-browser file system. +To `base64` encode images in Markdown cells, we needed to selectively inject our custom logic into the rendering pipeline. The base64-encoded images can be provided to the browser using the `data:` scheme which defines a ["data URL"](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). +Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface - we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our own in order to provide data URLs for user-uploaded images in JupyterLite. +To that end we [replaced][PR17784] the usage of the hard-coded class in JupyterLab with a dependency on a new plugin that would create (manufacture) URL resolvers. Such a refactor allowed to [swap the implementation in JupyterLite][PR1707] for one which resolved the local links to user-uploaded images to data URLs, enabling rendering of files from the in-browser file system. +[IResolver]: https://jupyterlab.readthedocs.io/en/stable/api/interfaces/rendermime.IRenderMime.IResolver.html +[UrlResolver]: https://jupyterlab.readthedocs.io/en/stable/api/classes/rendermime.RenderMimeRegistry.UrlResolver.html +[PR17784]: https://github.com/jupyterlab/jupyterlab/pull/17784 +[PR1707]: https://github.com/jupyterlite/jupyterlite/pull/1707 Once we were able to allow swapping the URL resolver with our own in JupyterLab, and the JupyterLab version that JupyterLite is based on was updated to support this, this was ported to JupyterLite pretty easily – the `resolveUrl` function for JupyterLite was updated to base64-encode the data for popular image file formats, allowing them to be rendered correctly within the notebook interface. From 6c578351e5ebad8220a147603731a0eb780f6561 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:18:21 +0100 Subject: [PATCH 06/31] Two more grammar fixes + verbose URL description --- apps/labs/posts/jupyter-everywhere.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index b07638831..ccd06c7fa 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -141,8 +141,9 @@ The Files widget is accessible via a sidebar tab, allowing users to easily switc However, as seamlessly as we expected it to work, we ran into a quirk: when uploading specific file types, such as images in PNG or JPEG format, the files would not render correctly when accessed in Markdown cells within the notebook. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for specific file formats. To address this, we implemented custom logic to set the correct MIME types when uploading and accessing these files, ensuring they rendered correctly in the notebook interface. To `base64` encode images in Markdown cells, we needed to selectively inject our custom logic into the rendering pipeline. The base64-encoded images can be provided to the browser using the `data:` scheme which defines a ["data URL"](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). -Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface - we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our own in order to provide data URLs for user-uploaded images in JupyterLite. -To that end we [replaced][PR17784] the usage of the hard-coded class in JupyterLab with a dependency on a new plugin that would create (manufacture) URL resolvers. Such a refactor allowed to [swap the implementation in JupyterLite][PR1707] for one which resolved the local links to user-uploaded images to data URLs, enabling rendering of files from the in-browser file system. +Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface – we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our own in order to provide data URLs for user-uploaded images in JupyterLite. +To that end, [we replaced the usage of the hard-coded UrlResolver component in JupyterLab][PR17784] with a dependency on a new plugin that would create (manufacture) URL resolvers. Such a refactor allowed us to [swap the URL resolver implementation in JupyterLite][PR1707] for one which resolved the local links to user-uploaded images to data URLs, enabling rendering of files from the in-browser file system. + [IResolver]: https://jupyterlab.readthedocs.io/en/stable/api/interfaces/rendermime.IRenderMime.IResolver.html [UrlResolver]: https://jupyterlab.readthedocs.io/en/stable/api/classes/rendermime.RenderMimeRegistry.UrlResolver.html [PR17784]: https://github.com/jupyterlab/jupyterlab/pull/17784 From 58be6470988a4146dadc58208fe8dda30df55e17 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:23:26 +0100 Subject: [PATCH 07/31] Change "dialogue" to "dialog" everywhere --- apps/labs/posts/jupyter-everywhere.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index ccd06c7fa..86dce1ca6 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -77,12 +77,12 @@ The sharing service provides two key features: To integrate the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others to view the notebook in read-only mode, without the ability to edit its contents. -Initially, this was implemented using a dialogue that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. +Initially, this was implemented using a dialog that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. Later in the development process, we decided it would be more user-friendly to generate friendlier URLs for sharing, rather than the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service initially generated. To achieve this, the sharing service implemented a system that produces human-readable IDs from combinations of aquatic animal names and adjectives, creating memorable, engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. - - + + There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e, a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. @@ -116,7 +116,7 @@ Hence, users need to upload notebooks manually to a JupyterLite instance before Here is a quick walkthrough of how it works behind the scenes. -When the user clicks the "Upload a Notebook" button, a file input dialogue is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. +When the user clicks the "Upload a Notebook" button, a file input dialog is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup. The notebook is read from `localStorage` and written to the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from `localStorage` to free up space, and no race conditions occur because the notebook is read only once during startup. @@ -192,7 +192,7 @@ However, Jupyter does not provide this functionality out of the box. + ## Some achievements and future directions From c7671f0d5cb8914139ece7299c7d9a2c903f9f42 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:11:07 +0100 Subject: [PATCH 08/31] Add problem description for landing page uploads --- apps/labs/posts/jupyter-everywhere.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 86dce1ca6..10c1c0c45 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -111,9 +111,13 @@ Since downloading notebooks is only half the story, you might ask – how does o Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means there is no visible file browser or file management interface in Jupyter Everywhere, to avoid unnecessary complexity. -Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. The main entry point for this is the Jupyter Everywhere landing page, where we've added an "Upload a Notebook" button that lets users select a local IPyNB file from their computer and upload it to the JupyterLite file system. +Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e. `/lab/` relative to the landing page. -Here is a quick walkthrough of how it works behind the scenes. +This means that not only do we need to provide a way for users to upload notebooks to JupyterLite before it starts up, but we also need to ensure that the uploaded notebooks are available in the user's Jupyter Everywhere session. + +We achieved this by adding an "Upload a Notebook" button that lets users select a local IPyNB file from their computer and upload it to the JupyterLite file system. + +Here is a quick walkthrough of how it works behind the scenes: When the user clicks the "Upload a Notebook" button, a file input dialog is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. From 7c9a888f856bb8472cb09b1df397cacafbfb1cb7 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:12:47 +0100 Subject: [PATCH 09/31] Add URL resolver problem description suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com> --- apps/labs/posts/jupyter-everywhere.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 10c1c0c45..dc17e5d69 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -142,7 +142,7 @@ The Files widget is accessible via a sidebar tab, allowing users to easily switc #### The quest for being able to render uploaded files within the notebook interface -However, as seamlessly as we expected it to work, we ran into a quirk: when uploading specific file types, such as images in PNG or JPEG format, the files would not render correctly when accessed in Markdown cells within the notebook. After some investigation, we discovered that this was due to the way JupyterLite handled MIME types for specific file formats. To address this, we implemented custom logic to set the correct MIME types when uploading and accessing these files, ensuring they rendered correctly in the notebook interface. +While the files could now be uploaded and accessed from the code cells, we ran into a quirk: uploaded images would not render correctly when embedded in Markdown cells within the notebook. We found that JupyterLite does not actually serve the user-uploaded files from local storage. After discussing the problem with other JupyterLite maintainers, we confirmed that there are two ways to solve it: serve the files from a Web Worker thread, or base64-encode the image contents during rendering (as we initially proposed). Due to compatibility concerns with Web Workers, we settled on a base64-encoding approach. To `base64` encode images in Markdown cells, we needed to selectively inject our custom logic into the rendering pipeline. The base64-encoded images can be provided to the browser using the `data:` scheme which defines a ["data URL"](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface – we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our own in order to provide data URLs for user-uploaded images in JupyterLite. @@ -153,8 +153,6 @@ To that end, [we replaced the usage of the hard-coded UrlResolver component in J [PR17784]: https://github.com/jupyterlab/jupyterlab/pull/17784 [PR1707]: https://github.com/jupyterlite/jupyterlite/pull/1707 -Once we were able to allow swapping the URL resolver with our own in JupyterLab, and the JupyterLab version that JupyterLite is based on was updated to support this, this was ported to JupyterLite pretty easily – the `resolveUrl` function for JupyterLite was updated to base64-encode the data for popular image file formats, allowing them to be rendered correctly within the notebook interface. - #### Working with remote files within the notebook interface From 7dd1bfa8cbc29f77646de3cbb9af42f9d8f91889 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:24:04 +0100 Subject: [PATCH 10/31] Update title --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index dc17e5d69..9275e6828 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -1,5 +1,5 @@ --- -title: 'Jupyter Everywhere: empowering interactive computing for K-12 education' # not final +title: 'Jupyter Everywhere: empowering interactive computing for high school students' published: November 01, 2025 authors: [agriya-khetarpal] description: 'The story of how Jupyter Everywhere is transforming K-12 education through interactive computing' From 235b362f8c844a494d9c50210c82a2bd1213a659 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:24:19 +0100 Subject: [PATCH 11/31] Link to nine-year old issue for per-cell run buttons --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 9275e6828..031e62c76 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -176,7 +176,7 @@ For the Pyodide kernel, we implemented a Jupyter Everywhere plugin that calls `p Popularised by platforms such as [Observable](https://observablehq.com/), Google Colaboratory, Kaggle Notebooks, and Deepnote, "Run" buttons next to code cells are a common feature in modern interactive computing environments. These buttons provide a convenient way for users to execute individual code cells with a single click, without needing to navigate to the toolbar or use keyboard shortcuts. -However, Jupyter does not provide this functionality out of the box. . +Unfortunately, however, Jupyter does not provide this functionality out of the box. This is a long-standing feature request in the Jupyter community, with various discussions on how to implement it effectively and in a manner that aligns with Jupyter's design principles, user experience, and accessibility standards: https://github.com/jupyterlab/jupyterlab/issues/2109 From cd20c4ee211fec7f244ba186504fdd7e559c58a8 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:24:33 +0100 Subject: [PATCH 12/31] Link to Mike's and Peyton's GitHub handles --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 031e62c76..6d7c08265 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -208,7 +208,7 @@ Having a fully functional Jupyter environment that runs entirely in the browser ## Acknowledgements -I, the author, am incredibly grateful to have had this opportunity to work on Jupyter Everywhere. It is one of the most exciting end user applications of JupyterLite and WebAssembly that I have had the pleasure of contributing to, and considering that this was my first avenue into the world of Jupyter and programming in TypeScript, JavaScript, and React, I am pretty proud of what we have achieved as a team and how much I learned along the way, thanks to Michał Krassowski and Peyton Murray for their mentorship and guidance throughout the project. It feels fantastic to see how far Jupyter has come. I believe there is no better way for me to contribute to its growth than by building applications that align with my personal values of enhancing accessibility and driving education through technology. +I, the author, am incredibly grateful to have had this opportunity to work on Jupyter Everywhere. It is one of the most exciting end user applications of JupyterLite and WebAssembly that I have had the pleasure of contributing to, and considering that this was my first avenue into the world of Jupyter and programming in TypeScript, JavaScript, and React, I am pretty proud of what we have achieved as a team and how much I learned along the way, thanks to [Michał Krassowski](https://github.com/krassowski) and [Peyton Murray](https://github.com/peytondmurray) for their mentorship and guidance throughout the project. It feels fantastic to see how far Jupyter has come, and I believe there is no better way for me to contribute to its growth than by building applications that align with my personal values of enhancing accessibility and driving education through technology. We lay down our gratitude to the following individuals and organisations for their invaluable contributions to the development and success of Jupyter Everywhere: From a61e5af344b10c0087d5a1ec0665e1a96f525285 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:33:09 +0100 Subject: [PATCH 13/31] Take a balanced stance w.r.t. JupyterHub/Nebari --- apps/labs/posts/jupyter-everywhere.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 6d7c08265..2fe64a4cf 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -40,12 +40,11 @@ This is what drives Jupyter Everywhere's philosophy: to provide a simplified, us ## Administrative and technical challenges in K-12 settings and high school districts -One of the primary challenges in high school districts is the lack of technical infrastructure and support for setting up and maintaining Jupyter environments, especially when dealing with a large number of students and educators. Many schools may lack dedicated IT staff or resources to manage JupyterHub installations, leading to potential downtime and an inconsistent user experience. A lot of this may be outsourced to third-party vendors who are experienced in setting up learning management systems (LMS) and JupyterHub instances, but this can be costly and may not always align with the specific needs of the school or district. -Additionally, ensuring that every student has access to the necessary software and libraries can be a logistical nightmare, especially when students are assigned a standard set of hardware and must be configured identically. +One of the primary challenges in high school districts is the varying levels of technical infrastructure and IT support available for educational technology deployments. While solutions like JupyterHub provide powerful multi-user environments that work well in university settings with dedicated IT teams and infrastructure, K-12 schools often face different constraints. Many schools have limited IT staffing focused on maintaining core systems like student information databases, learning management platforms, and device fleets for hundreds or thousands of students. -To think about JupyterHub in such settings is a no-go: it invites further complexity, requiring user authentication, server management, and resource allocation. +Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance—investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. -We identify three key challenges that Jupyter Everywhere aims to address: +We identify three key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. - Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. - Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. From d804e14d2cd93f97bce8f2c576067ec7a5af945e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:37:08 +0100 Subject: [PATCH 14/31] Fix comments syntax for deploy preview to work Co-Authored-By: Tania Allard <23552331+trallard@users.noreply.github.com> --- apps/labs/posts/jupyter-everywhere.md | 54 +++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 2fe64a4cf..7e40a6d82 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -16,7 +16,7 @@ hero: --- # The Jupyter Everywhere story: empowering interactive computing for K-12 education - +{/* the title is tentative and not final */} Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), a notebooks-based end-to-end application for high school (K-12) students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). @@ -34,7 +34,7 @@ Jupyter provides two traditional user interfaces: Jupyter Notebook and JupyterLa However, both interfaces are designed with a certain level of technical proficiency in mind, especially for newcomers to programming and interactive computing. Myriad features and options can overwhelm many a novice, leading to confusion and frustration. For K-12 educators and students who may not have prior experience with Jupyter or programming in general, the learning curve associated with these interfaces is steeper than anticipated in an educational setting. - +{/* image of Jupyter user interface with a 75 degree diagonal slash through half of it, with the right half representing a graphic indicating complicated clockwork and gears? my idea is to show how Jupyter's UI can be compared to a pictorial representation of multiple knobs and switches */} This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! @@ -54,7 +54,7 @@ We identify three key challenges that Jupyter Everywhere aims to address specifi Here, we start by describing our journey building the application from the ground up – from a no-frills prototype to a full-fledged JupyterLite extension. - +{/* architecture diagram here */} ### Customising JupyterLite, and key features of Jupyter Everywhere @@ -70,7 +70,7 @@ The sharing service provides two key features: - Uploading notebooks to a server for storage and sharing - Generating sharing IDs based on a hash of the current session and the notebook state, and readable IDs as a mnemonic for sharing purposes via a database of aquatic animal names and adjectives to make it more fun and engaging for students. - +{/* Add diagram from Adam's architecture-overview.md on Slack showing how the sharing service works with Jupyter Everywhere */} #### Sharing notebooks via view-only links @@ -80,8 +80,8 @@ Initially, this was implemented using a dialog that generated a password-protect Later in the development process, we decided it would be more user-friendly to generate friendlier URLs for sharing, rather than the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service initially generated. To achieve this, the sharing service implemented a system that produces human-readable IDs from combinations of aquatic animal names and adjectives, creating memorable, engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. - - +{/* add image of share dialog opened by clicking the "Share" button */} +{/* alt text: The share dialog in Jupyter Everywhere, opened by clicking the "Share" button in the toolbar, allowing users to generate view-only links for sharing their notebooks, and a "Copy link!" button to copy the generated link to the user's clipboard. */} There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e, a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. @@ -91,7 +91,7 @@ When a user opens a shared notebook link, they are presented with a read-only vi This was trickier to implement than we initially thought. At that time, JupyterLab did not expose a command for opening a notebook without a kernel attached. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override JupyterLab's default behaviour to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. - +{/* A section on the problem of non-persistence in Jupyter Everywhere due to no user accounts + how we addressed it */} #### Notebook downloads @@ -108,7 +108,7 @@ Thus, we went forward with a custom approach for this, based on [in-progress wor Since downloading notebooks is only half the story, you might ask – how does one upload notebooks that might have been created elsewhere (whether in Jupyter Notebook, JupyterLab, or another JupyterLite instance, or Jupyter Everywhere itself), into Jupyter Everywhere? Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means there is no visible file browser or file management interface in Jupyter Everywhere, to avoid unnecessary complexity. - +{/* link to simple interface docs in jupyter */} Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e. `/lab/` relative to the landing page. @@ -117,7 +117,7 @@ This means that not only do we need to provide a way for users to upload noteboo We achieved this by adding an "Upload a Notebook" button that lets users select a local IPyNB file from their computer and upload it to the JupyterLite file system. Here is a quick walkthrough of how it works behind the scenes: - +{/* add pictures */} When the user clicks the "Upload a Notebook" button, a file input dialog is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. @@ -131,7 +131,7 @@ When working with Jupyter notebooks, especially in data science and education co Jupyter Everywhere design called for a simplified interface, with the default file browser available in JupyterLite replaced by a dedicated Files tab. We implemented it in another plugin, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. - +{/* various screenshots for files widget functionality sprinkled in between */} The files are displayed in a grid, akin to a file browser on phones and desktop computers, with each file shown as a tile containing a placeholder and its associated filename. @@ -152,7 +152,7 @@ To that end, [we replaced the usage of the hard-coded UrlResolver component in J [PR17784]: https://github.com/jupyterlab/jupyterlab/pull/17784 [PR1707]: https://github.com/jupyterlite/jupyterlite/pull/1707 - +{/* mention the PRs that addressed these issues upstream in the above paragraphs */} #### Working with remote files within the notebook interface @@ -160,14 +160,14 @@ Another common use case when working with Jupyter notebooks is accessing remote However, notebooks in JupyterLite rely on Pyodide and xeus-r to provide programmatic interfaces akin to those of Python and R, respectively. There are a few differences between these in-browser runtime environments and traditional Jupyter kernels running on a server. - +{/* discuss difference very briefly between Pyodide networking and Python networking here, link to Pyodide docs */} For instance, Pyodide provides a built-in `pyodide.http` module that allows users to fetch remote files using the browser's native `fetch` API, which is asynchronous and non-blocking. This means that users can download remote files and read them into their notebooks without blocking the main thread of execution. - +{/* add some small parts about using pyodide.http, pyodide-http module for monkeypatching urllib, urllib3 and requests */} -For xeus-r and R, networking support was added in later versions. - +For xeus-r and R, networking support was added in later versions. +{/* should we describe what changed in a sentence? */} For the Pyodide kernel, we implemented a Jupyter Everywhere plugin that calls `pyodide_http.patch_all()` silently when the kernel first starts up. For students and educators, this means they can use familiar public APIs from scientific Python and data analysis libraries, such as the `pandas.read_csv()` function, to read remote CSV files directly into their notebooks without needing to know the underlying implementation details. @@ -177,29 +177,29 @@ Popularised by platforms such as [Observable](https://observablehq.com/), Google Unfortunately, however, Jupyter does not provide this functionality out of the box. This is a long-standing feature request in the Jupyter community, with various discussions on how to implement it effectively and in a manner that aligns with Jupyter's design principles, user experience, and accessibility standards: https://github.com/jupyterlab/jupyterlab/issues/2109 - +{/* describe our solution here and what it does in two paragraphs, and link to the PR */} ## A call to action for educators and institutions - +{/* A message to the world, unsure what I'd like to add here... */} ## What we learned from developing and deploying Jupyter Everywhere 1. Test early, and test well. - - - +{/* describe mocking saga and the infrastructure/deployments we used? */} +{/* describe bug here with sharing a view-only notebook not working because it was not skipping calling the sharing service, which we did not recognise because we were using mocks */} +{/* anything else? */} 2. User experience is paramount. Ask educators and students for feedback and iterate on the design based on their needs. Jupyter is fundamentally a tool for learning, but even it would be rendered useless if it were too difficult for students or educators to use. Given that we deal with high school students, we have to be especially careful to ensure the user experience is as intuitive as possible and aligns with what someone with little to no programming experience, let alone literate programming experience, would expect from a web application. 3. Do not be afraid to pivot. We initially started with a set approach to building Jupyter Everywhere. Still, we were carefully discerning of our inner instincts and the feedback we received from our collaborators, and were not afraid to change our approach when necessary. This flexibility enabled us to adapt to changing requirements and deliver a product that truly met our users' needs. - +{/* add point-wise description about toast notifications, leave confirmation dialog, file tiles, sharing functionality, view-only notebooks, kernel and notebook URL parameters, separate URL for files widget, and all the things we did that were out of the scope of work */} ## Some achievements and future directions Having a fully functional Jupyter environment that runs entirely in the browser is already a significant achievement. However, there is always room for improvement and expansion for future directions. Potential areas for future development could include: - +{/* Gotta describe the following: */} - Per-cell stop buttons and full support for interrupting code execution for in-browser kernels - Real-time collaboration in JupyterLite - Improvements to the sharing service @@ -220,7 +220,7 @@ We lay down our gratitude to the following individuals and organisations for the - CourseKata for their design and development of the sharing service, and infrastructure support for hosting Jupyter Everywhere on AWS - The QuantStack team for their work on JupyterLite, the Xeus project, xeus-r https://blog.jupyter.org/r-in-the-browser-announcing-our-webassembly-distribution-9450e9539ed5 - +{/* individuals here? */} ## References @@ -228,7 +228,7 @@ We lay down our gratitude to the following individuals and organisations for the [2] Pyodide: https://pyodide.org/en/stable/ - - - - +{/* unsure of the inclusion of the following */} +{/* [3] Skew The Script: https://skewthescript.org/ */} +{/* [4] CourseKata: https://coursekata.com/ */} +{/* and any more references as needed */} From dbc6426f4778117d69864473106327001646f3b4 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:51:44 +0100 Subject: [PATCH 15/31] Mention that we have no use for JupyterHub's flexibility Co-Authored-By: Peyton Murray --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 7e40a6d82..2d9e7e5f0 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -42,7 +42,7 @@ This is what drives Jupyter Everywhere's philosophy: to provide a simplified, us One of the primary challenges in high school districts is the varying levels of technical infrastructure and IT support available for educational technology deployments. While solutions like JupyterHub provide powerful multi-user environments that work well in university settings with dedicated IT teams and infrastructure, K-12 schools often face different constraints. Many schools have limited IT staffing focused on maintaining core systems like student information databases, learning management platforms, and device fleets for hundreds or thousands of students. -Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance—investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. +Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance—investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning is emphasised. We identify three key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. From ba9196d4e46e03fe7200e4ee24e4509a012934b0 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:51:56 +0100 Subject: [PATCH 16/31] Simplify sharing service sentence Co-Authored-By: Peyton Murray --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 2d9e7e5f0..c2b1c92ca 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -64,7 +64,7 @@ Initially, we started developing the application as a JupyterLab extension to pr ### Facilitating notebook sharing and distribution -Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service allows authentication through various API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as `.ipynb` files, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. +Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service provides API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as `.ipynb` files, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. The sharing service provides two key features: - Uploading notebooks to a server for storage and sharing From 0bbcfaab08baf655bd27b8fbb8f18675895141fc Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:52:06 +0100 Subject: [PATCH 17/31] Add a link to `jsPDF`'s GitHub repository Co-Authored-By: Peyton Murray --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index c2b1c92ca..08dbaac0c 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -101,7 +101,7 @@ However, we wanted to go a step further and allow users to download notebooks in Thus, we went forward with a custom approach for this, based on [in-progress work in the JupyterLite community](https://github.com/jupyterlite/jupyterlite/pull/1625) to add PDF export functionality using an in-browser PDF generation library called `jsPDF`. -`jsPDF` is a JavaScript library that allows generating PDF documents directly in the browser, without server-side processing. We integrated `jsPDF` into Jupyter Everywhere via a custom download button that converts the current notebook to a PDF and triggers its download. However, this functionality is still a work in progress. There is work to be done: improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. +[`jsPDF`](https://github.com/parallax/jsPDF) is a JavaScript library that allows generating PDF documents directly in the browser, without server-side processing. We integrated `jsPDF` into Jupyter Everywhere via a custom download button that converts the current notebook to a PDF and triggers its download. However, this functionality is still a work in progress. There is work to be done: improving the fidelity of the generated PDFs, especially for complex notebooks with rich visualisations, widgets, and typeset LaTeX content. #### Uploading notebooks to Jupyter Everywhere From 967effe94166b331c2b10f26f5eeeaff4e6f10d9 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:52:20 +0100 Subject: [PATCH 18/31] Fix typo; add apostrophe Co-Authored-By: Peyton Murray --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 08dbaac0c..c08382f62 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -129,7 +129,7 @@ We then redirect the user away from the Jupyter Everywhere landing page and to t When working with Jupyter notebooks, especially in data science and education contexts, access to tabular data files CSV/TSV and image files, whether local or remote, is taken for granted. In a traditional Jupyter environment, these files can be easily uploaded to the server's file system and accessed within the notebook. -Jupyter Everywhere design called for a simplified interface, with the default file browser available in JupyterLite replaced by a dedicated Files tab. We implemented it in another plugin, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. +Jupyter Everywhere's design called for a simplified interface, with the default file browser available in JupyterLite replaced by a dedicated Files tab. We implemented it in another plugin, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. {/* various screenshots for files widget functionality sprinkled in between */} From 0510bc4a04014c048546c762077978193690c7ee Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:02:06 +0100 Subject: [PATCH 19/31] Add funding note about STS Co-Authored-By: Peyton Murray --- apps/labs/posts/jupyter-everywhere.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index c08382f62..4a6975389 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -213,10 +213,11 @@ We lay down our gratitude to the following individuals and organisations for the - Jupyter ecosystem developers and maintainers - JupyterLite developers and maintainers -- Educators and students who provided feedback during development, courtesy Skew The Script +- Educators and students who provided feedback after the initial development phase, courtesy Skew The Script - Gates Foundation https://www.gatesfoundation.org/about/committed-grants/2024/03/inv-067942 for their grant award to Skew The Script to support the development of Jupyter Everywhere - The Scientific Python ecosystem - The core developers and maintainers of Pyodide (how do I do this without sounding snobbish, as I am a Pyodide maintainer myself?) +- Skew The Script for the funding they provided for this initiative, and their vision for it, their collaboration, and feedback throughout the development cycle - CourseKata for their design and development of the sharing service, and infrastructure support for hosting Jupyter Everywhere on AWS - The QuantStack team for their work on JupyterLite, the Xeus project, xeus-r https://blog.jupyter.org/r-in-the-browser-announcing-our-webassembly-distribution-9450e9539ed5 From 1eb31a580a4fe3ee3ed5723af5a84b66087a6c2f Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:18:58 +0100 Subject: [PATCH 20/31] Switch an em-dash with an en-dash --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 4a6975389..138a5782f 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -42,7 +42,7 @@ This is what drives Jupyter Everywhere's philosophy: to provide a simplified, us One of the primary challenges in high school districts is the varying levels of technical infrastructure and IT support available for educational technology deployments. While solutions like JupyterHub provide powerful multi-user environments that work well in university settings with dedicated IT teams and infrastructure, K-12 schools often face different constraints. Many schools have limited IT staffing focused on maintaining core systems like student information databases, learning management platforms, and device fleets for hundreds or thousands of students. -Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance—investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning is emphasised. +Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance – investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning is emphasised. We identify three key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. From 3d6ffb66384aa25642ebf4b95245279ad68c1c17 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:19:20 +0100 Subject: [PATCH 21/31] Four key challenges, not three --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 138a5782f..a594ac7dd 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -44,7 +44,7 @@ One of the primary challenges in high school districts is the varying levels of Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance – investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning is emphasised. -We identify three key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: +We identify four key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. - Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. - Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. From fd3539ff2d7cb03b26de8a661cbab672d1d3c0cb Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:23:29 +0100 Subject: [PATCH 22/31] Fix typos --- apps/labs/posts/jupyter-everywhere.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index a594ac7dd..c359d5dc1 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -83,7 +83,7 @@ Later in the development process, we decided it would be more user-friendly to g {/* add image of share dialog opened by clicking the "Share" button */} {/* alt text: The share dialog in Jupyter Everywhere, opened by clicking the "Share" button in the toolbar, allowing users to generate view-only links for sharing their notebooks, and a "Copy link!" button to copy the generated link to the user's clipboard. */} -There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e, a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. +There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e., a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click the link to view the notebook in their web browser, without installing any additional software or creating an account. @@ -110,7 +110,7 @@ Since downloading notebooks is only half the story, you might ask – how does o Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means there is no visible file browser or file management interface in Jupyter Everywhere, to avoid unnecessary complexity. {/* link to simple interface docs in jupyter */} -Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e. `/lab/` relative to the landing page. +Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e., `/lab/` relative to the landing page. This means that not only do we need to provide a way for users to upload notebooks to JupyterLite before it starts up, but we also need to ensure that the uploaded notebooks are available in the user's Jupyter Everywhere session. From 5a97e76a49c13688bc872e6f812e79fcb3e759b0 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:23:42 +0100 Subject: [PATCH 23/31] Add comma --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index c359d5dc1..412cdf5d9 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -76,7 +76,7 @@ The sharing service provides two key features: To integrate the sharing service, we embedded a "Share" button in the Jupyter Everywhere interface. It connects to the sharing service API to upload the current notebook and generate a shareable link. This link can be shared with others to view the notebook in read-only mode, without the ability to edit its contents. -Initially, this was implemented using a dialog that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. +Initially, this was implemented using a dialog that generated a password-protected sharing link. The password would have to be shared separately with the recipient. However, to streamline the user experience, we later transitioned to generating view-only links that do not require a password. This change simplified the sharing process, making it easier for users to distribute their notebooks without the added step of managing or remembering passwords, which were deemed unnecessary, since view-only links inherently restrict editing capabilities and access. It also reduces users' cognitive load by eliminating the need to remember or manage additional credentials when a link would suffice for viewing. Later in the development process, we decided it would be more user-friendly to generate friendlier URLs for sharing, rather than the default UUID-based URLs that are not easy to remember or share verbally, which the sharing service initially generated. To achieve this, the sharing service implemented a system that produces human-readable IDs from combinations of aquatic animal names and adjectives, creating memorable, engaging URLs for sharing notebooks. This approach not only enhances the user experience but also adds a fun element to the sharing process, making it more appealing for students and educators alike. We integrated the sharing service's response to use these "readable IDs" in the interface by default. From fb625c2cbf9b986db7b84ea9a56b58c0d519bc43 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:24:46 +0100 Subject: [PATCH 24/31] Clarify the act of sharing as a snapshot --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 412cdf5d9..7e9d5724a 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -83,7 +83,7 @@ Later in the development process, we decided it would be more user-friendly to g {/* add image of share dialog opened by clicking the "Share" button */} {/* alt text: The share dialog in Jupyter Everywhere, opened by clicking the "Share" button in the toolbar, allowing users to generate view-only links for sharing their notebooks, and a "Copy link!" button to copy the generated link to the user's clipboard. */} -There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e., a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it. +There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e., a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it, or ask the recipient(s) to refresh the page to see the latest snapshot. Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click the link to view the notebook in their web browser, without installing any additional software or creating an account. From 8c91b74da229de9946dd6eaa69bbdd56420b64be Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 30 Oct 2025 06:38:44 +0100 Subject: [PATCH 25/31] Active voice, not passive, add parentheses --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 7e9d5724a..81de54a49 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -127,7 +127,7 @@ We then redirect the user away from the Jupyter Everywhere landing page and to t #### The Files widget -When working with Jupyter notebooks, especially in data science and education contexts, access to tabular data files CSV/TSV and image files, whether local or remote, is taken for granted. In a traditional Jupyter environment, these files can be easily uploaded to the server's file system and accessed within the notebook. +When working with Jupyter notebooks, especially in data science and education contexts, it is easy to take access to tabular data files (CSV/TSV) and image files, whether local or remote, for granted. In a traditional Jupyter environment, these files can be conveniently uploaded to the server's file system and accessed within the notebook. Jupyter Everywhere's design called for a simplified interface, with the default file browser available in JupyterLite replaced by a dedicated Files tab. We implemented it in another plugin, extending Jupyter's `MainAreaWidget`. This widget provides an "add new" button that allows users to upload data files from their local computer into the JupyterLite in-browser file system. Once uploaded, the files are accessible from within the notebook, allowing users to read and manipulate data as needed. From ca28e2078fb53d9bf7b396a23e0309edb0c327b6 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:20:36 +0100 Subject: [PATCH 26/31] Also mention xeus-r in the intro besides Pyodide --- apps/labs/posts/jupyter-everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 81de54a49..11540de82 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -20,7 +20,7 @@ hero: Hi! I am Agriya Khetarpal, a software engineer at Quansight PBC. In this blog post, I will share the story of how we built [Jupyter Everywhere](https://jupytereverywhere.org/), a notebooks-based end-to-end application for high school (K-12) students in the U.S.A., in collaboration with [Skew The Script](https://skewthescript.org/) and [CourseKata](https://coursekata.com/). -Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – technologies that enable running Jupyter notebooks entirely in a web browser. In particular, we use [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack, to execute data science and statistical computing code in a browser without provisioning any server-side dependencies or deployments. +Jupyter Everywhere aims to make interactive computing accessible to students and educators, regardless of their technical background or resources. We stand on the shoulders of giants, leveraging the power of [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) and [WebAssembly (WASM)](https://webassembly.org/) – technologies that enable running Jupyter notebooks entirely in a web browser. In particular, we harness [Pyodide](https://pyodide.org/en/stable/), a WASM-based distribution of Python with a rich scientific stack; and [Xeus-R](https://blog.jupyter.org/r-in-the-browser-announcing-our-webassembly-distribution-9450e9539ed5), a WASM-based R kernel for Jupyter notebooks; to execute data science and statistical computing code in a browser without provisioning any server-side dependencies or deployments. I'll discuss the challenges we faced, the features we implemented, and the lessons we learned while developing Jupyter Everywhere. I hope that this post will inspire educators, developers, and institutions to explore the potential of interactive computing in education, and to contribute to the open source ecosystems that make it all possible and worthwhile. From d97b647348f530533a13c60f2f88d0c6c1a011b6 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:07:32 +0100 Subject: [PATCH 27/31] Add URL parameters section --- apps/labs/posts/jupyter-everywhere.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 11540de82..3ff08bdfa 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -179,6 +179,20 @@ Unfortunately, however, Jupyter does not provide this functionality out of the b {/* describe our solution here and what it does in two paragraphs, and link to the PR */} +### The dance of URL parameters + +We use URL parameters extensively in Jupyter Everywhere to extend the plugins and customise the user experience based on the context in which the application is launched. For instance, we use URL parameters to specify which notebook to open upon startup, whether to launch in view-only mode, and other configuration options that affect the behaviour of the application. + +URL parameters are query strings appended to the URL after a question mark (`?`), consisting of key-value pairs separated by ampersands (`&`). They provide a method for passing information to web applications in a stateless manner, allowing for dynamic configuration without the need for server-side state management. + +Here is a description of some of the key URL parameters used in Jupyter Everywhere. We use these parameters to control various aspects of the application's behaviour and user experience, as described below: + +- `notebook`: Specifies the UUID of the notebook to open upon startup by calling the sharing service API. This parameter displays view-only notebooks if loaded, as the sharing service does not possess a token to authenticate the user. + At the same time, we append this URL parameter as soon as the notebook is shared, so that users can share the URL directly if they wish to. +- `uploaded-notebook`: Specifies the UUID of a notebook that has been uploaded via the "Upload a Notebook" button on the landing page. This parameter allows users to upload a local IPyNB file from their computer and have it opened automatically in Jupyter Everywhere upon startup. This URL parameter is removed immediately after the notebook is read from `localStorage` and written to the JupyterLite in-browser file system. +- `kernel`: Specifies the kernel to use for executing code cells in the notebook. This parameter allows users to select between `python` (Python/Pyodide) and `r` (xeus-r/R) kernels, and the default is `python`, so if the parameter is omitted, the Pyodide kernel is used. This URL parameter is also appended to the URL when the user selects a different kernel from the kernel selection dropdown in the notebook toolbar, and is immediately removed as it is strictly an implementation detail. For uploaded notebooks, we utilise the notebook's metadata to determine the kernel to use, and pass that information via this URL parameter during startup. +- `tab`: Specifies which sidebar tab to open upon startup. This parameter allows users to choose between the "Files" (`files`) tab and the "Notebook" tab, with the default being the "Notebook" tab. This URL parameter is also appended to the URL when the user switches between the sidebar tabs, allowing for deep linking to specific tabs within the application. We also use this parameter to provide a `404` tab, which redirects to a custom "Not found" page if the specified resource does not exist. + ## A call to action for educators and institutions {/* A message to the world, unsure what I'd like to add here... */} From 82ac79adb59efeaba1010e2484241aef9613497b Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:24:48 +0100 Subject: [PATCH 28/31] Add section on user experience and data safety --- apps/labs/posts/jupyter-everywhere.md | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 3ff08bdfa..cd624e9d7 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -193,6 +193,36 @@ Here is a description of some of the key URL parameters used in Jupyter Everywhe - `kernel`: Specifies the kernel to use for executing code cells in the notebook. This parameter allows users to select between `python` (Python/Pyodide) and `r` (xeus-r/R) kernels, and the default is `python`, so if the parameter is omitted, the Pyodide kernel is used. This URL parameter is also appended to the URL when the user selects a different kernel from the kernel selection dropdown in the notebook toolbar, and is immediately removed as it is strictly an implementation detail. For uploaded notebooks, we utilise the notebook's metadata to determine the kernel to use, and pass that information via this URL parameter during startup. - `tab`: Specifies which sidebar tab to open upon startup. This parameter allows users to choose between the "Files" (`files`) tab and the "Notebook" tab, with the default being the "Notebook" tab. This URL parameter is also appended to the URL when the user switches between the sidebar tabs, allowing for deep linking to specific tabs within the application. We also use this parameter to provide a `404` tab, which redirects to a custom "Not found" page if the specified resource does not exist. +### User experience (UX) and data safety utilities + +User experience is built into the core philosophy and technical design of Jupyter Everywhere. As discussed, our target audience is high school students, many of whom may be new to programming and interactive computing, which led to us having prioritised creating an intuitive and user-friendly interface that minimises cognitive load and maximises engagement, while emphasising aspects during the development cycle that enhance usability and accessibility. This reflects in how we handle data safety and loss prevention, given that Jupyter Everywhere works without user accounts or persistent storage. Read on to find out how we improvised such features in cool ways! + +#### Leave confirmation dialogs + +Jupyter Everywhere uses the [`memoryStorageDriver](https://jupyterlite.readthedocs.io/en/stable/howto/configure/storage.html#local-storage-drivers) by default, which stores user data in the browser's memory in an ephemeral manner. This means that when the user closes the browser tab or window, all their data can be lost. To mitigate this risk, we iterated on the user experience on various occasions to add various utilities that help students avoid losing their work inadvertently, since we decided not to use an account-based system for storing user data and notebooks. + +The first of these is a confirmation dialog that appears when the user attempts to close the browser tab or window while there are unsaved changes in the notebook. This dialog prompts the user to confirm their intention to leave the page, giving them a chance to save their work before exiting. + +{/* screenshot of the leave confirmation dialog */} + +#### Auto-save and continuous backup, and keeping the notebook URL parameter up to date + +Additionally, Jupyter provides an auto-save feature that automatically saves the notebook at a regular interval (every thirty seconds by default). We have connected the save event to the sharing service, so that whenever the notebook is auto-saved, the sharing service is called to upload the latest version of the notebook to the server. This ensures that the user's work is continuously backed up, reducing the risk of data loss due to unexpected closures or crashes, accidental navigation away from the page, or the act of forgetting to save manually. + +Here, we append the `notebook` URL parameter to the URL whenever the notebook is auto-saved, ensuring that the URL always points to the latest saved version of the notebook. If the user accidentally closes the tab or window, or reloads, Jupyter Everywhere will open and they can continue where they left off by creating a copy of the latest saved notebook contents retried from the sharing service. + +#### Toast notifications for save reminders and kernel switches + +JupyterLab provides a built-in notification system that allows extensions to display messages to users in a non-intrusive manner on the bottom-right corner. After many discussions on the topic, we have carefully utilised this functionality to keep users informed of the state of their notebooks. We display a toast notification for save reminders. That is, when the user makes changes to the notebook and has not copied the sharing link yet, we remind them to save their work by clicking the "Share" button and copying the link. This notification appears on "dirty" events, i.e., when the notebook has unsaved changes, and disappears once the user clicks the "Share" button. The timing and frequency of these notifications have been carefully calibrated to avoid overwhelming the user while still providing timely reminders. We use a five-minute interval between such notifications, which we found to be a good balance between being helpful and not intrusive. + +Similarly, we display toast notifications when the user switches kernels. For instance, if the user switches from the Pyodide kernel to the xeus-r kernel, we display a notification informing them that the kernel has been switched and that they may need to rerun the notebook for the changes to take effect. Jupyter uses non-reactive kernels, and hence does not automatically rerun the notebook when the kernel is switched, as this could lead to unexpected behaviour and data loss. Students are not aware of such nuances, especially when working with notebooks for the first time. Therefore, we found it important to inform them about this behaviour such that they can appropriately start writing code in a different programming language and note that the previous code cells will not have been executed in the new kernel, and that they may need to rerun the notebook from the start to ensure that all dependencies and variables are correctly defined in the new kernel, including different outputs and visualisations. + +#### Leave warnings when there are uploaded files present + +Browsers nowadays provide a built-in mechanism to warn users when they attempt to close a tab or window while there are unsaved changes in a form or input field. However, this mechanism does not cover all scenarios, especially when dealing with custom widgets and components within web applications, and has to be implemented manually using a [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) event listener. + +Particularly, we found that users might upload files to the Files widget without having any unsaved changes in the notebook itself. In such cases, the built-in browser mechanism would not trigger a warning when the user attempts to close the tab or window, potentially leading to data loss if the user forgets to download their uploaded files before exiting. We addressed this by connecting a `beforeunload` event listener to the Files widget's internal state, which tracks whether there is at least one uploaded file present. If so, the event listener now prompts the user with the browser's default message when they attempt to close the tab or window, thus serving as a reminder to ensure that they have fulfilled any necessary actions, such as downloading their uploaded files, before exiting. + ## A call to action for educators and institutions {/* A message to the world, unsure what I'd like to add here... */} From e28eed654e8c27964b1a71bf929ca94d0aa27336 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:25:04 +0100 Subject: [PATCH 29/31] Add brief WIP section on per-cell run buttons --- apps/labs/posts/jupyter-everywhere.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index cd624e9d7..317c1a4b2 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -166,6 +166,8 @@ For instance, Pyodide provides a built-in `pyodide.http` module that allows user {/* add some small parts about using pyodide.http, pyodide-http module for monkeypatching urllib, urllib3 and requests */} +{/* add permalink to pyodide-http module repo and patch_all function */} + For xeus-r and R, networking support was added in later versions. {/* should we describe what changed in a sentence? */} @@ -177,7 +179,11 @@ Popularised by platforms such as [Observable](https://observablehq.com/), Google Unfortunately, however, Jupyter does not provide this functionality out of the box. This is a long-standing feature request in the Jupyter community, with various discussions on how to implement it effectively and in a manner that aligns with Jupyter's design principles, user experience, and accessibility standards: https://github.com/jupyterlab/jupyterlab/issues/2109 -{/* describe our solution here and what it does in two paragraphs, and link to the PR */} +Building on prior art that has explored this feature in PRs X and Y (TODO link them), we implemented a plugin that adds "Run" buttons next to each code cell in the notebook interface. This involves creating a factory that produces a button widget in the input area of each code cell. The button is styled as an orange circular button with a white "play" icon. It is positioned to the left of the code cell input area. + +When the user clicks the "Run" button, it triggers the execution of the corresponding code cell, just like using the "Run" command from the toolbar or keyboard shortcuts, through the `notebook:run-cell` command in JupyterLab's command registry. + +{/* add link to our PR, add a screenshot of it where it shows up on code and markdown cells, and not on raw cells */} ### The dance of URL parameters From 5ecd8f29b44263948805c99cdb14846c03b20e9a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:25:19 +0100 Subject: [PATCH 30/31] Add notes on how view-only notebooks work --- apps/labs/posts/jupyter-everywhere.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 317c1a4b2..60bd3f47b 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -93,6 +93,14 @@ This was trickier to implement than we initially thought. At that time, JupyterL {/* A section on the problem of non-persistence in Jupyter Everywhere due to no user accounts + how we addressed it */} +Now that we are well-versed with view-only notebooks, let's explore how they function under the hood. + +When a user opens a view-only notebook link, Jupyter Everywhere parses the URL to extract the notebook's unique identifier. It then sends a request to the sharing service API to fetch the notebook contents associated with that identifier. The sharing service verifies the request and returns the notebook data in JSON format. + +We then create a new notebook widget using a factory that produces read-only, immutable notebooks, i.e., notebooks with the `editable` property set to `false` for all cells. This widget is then populated with the notebook contents retrieved from the sharing service. Since no kernel is associated with the notebook tracker, the notebook is rendered in a read-only state, preventing any modifications or code execution. + +The kernel selection dropdown in the notebook toolbar is disabled, as no kernel is available for executing code cells. The user can still navigate through the notebook, view outputs, and interact with any visualisations or widgets embedded in the notebook, but modifying the notebook's contents is only possible by creating a copy of the notebook in their own Jupyter Everywhere session. + #### Notebook downloads Another method for sharing notebooks is to export them from Jupyter Everywhere. This is facilitated by the built-in JupyterLite functionality to download notebooks in IPyNB format, which required adding a button to the notebook panel toolbar. From bfd6069f7a56af7a5ff1a216537f0b3eb1cab400 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:27:36 +0100 Subject: [PATCH 31/31] Some more suggestions from Grammarly --- apps/labs/posts/jupyter-everywhere.md | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/apps/labs/posts/jupyter-everywhere.md b/apps/labs/posts/jupyter-everywhere.md index 60bd3f47b..940c8c9ab 100644 --- a/apps/labs/posts/jupyter-everywhere.md +++ b/apps/labs/posts/jupyter-everywhere.md @@ -30,25 +30,25 @@ Jupyter notebooks have, for a long time, revolutionised the way we teach and lea ## Navigating the complexities of the Jupyter user interface -Jupyter provides two traditional user interfaces: Jupyter Notebook and JupyterLab, each with its own strengths and weaknesses. Jupyter Notebook offers a straightforward interface, ideal for beginners and quick prototyping. On the other hand, JupyterLab provides a more powerful and flexible environment, catering to advanced users who require features such as multi-document editing, integrated terminals, and plugin extensibility. +Jupyter provides two traditional user interfaces: Jupyter Notebook and JupyterLab, each with its own strengths and weaknesses. Jupyter Notebook offers a straightforward interface, making it ideal for beginners and quick prototyping. On the other hand, JupyterLab provides a more powerful and flexible environment, catering to advanced users who require features such as multi-document editing, integrated terminals, and plugin extensibility. However, both interfaces are designed with a certain level of technical proficiency in mind, especially for newcomers to programming and interactive computing. Myriad features and options can overwhelm many a novice, leading to confusion and frustration. For K-12 educators and students who may not have prior experience with Jupyter or programming in general, the learning curve associated with these interfaces is steeper than anticipated in an educational setting. {/* image of Jupyter user interface with a 75 degree diagonal slash through half of it, with the right half representing a graphic indicating complicated clockwork and gears? my idea is to show how Jupyter's UI can be compared to a pictorial representation of multiple knobs and switches */} -This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment tailored for educational use cases. For example, the Scratch programming language for children provides a visual programming interface that simplifies coding concepts, making it more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! +This is what drives Jupyter Everywhere's philosophy: to provide a simplified, user-friendly interface that abstracts away the complexities of Jupyter, while retaining its core functionalities with sensible defaults and features akin to an integrated development environment tailored for educational use cases. For example, the Scratch programming language, designed for children, provides a visual programming interface that simplifies coding concepts, making them more accessible to young learners. Similarly, Skew The Script has strived to create an intuitive interface that feels playful (think: an octopus mascot) yet powerful enough to run anything a student might throw at it. We have brought this to life by customising JupyterLite. Read on to find out how! ## Administrative and technical challenges in K-12 settings and high school districts -One of the primary challenges in high school districts is the varying levels of technical infrastructure and IT support available for educational technology deployments. While solutions like JupyterHub provide powerful multi-user environments that work well in university settings with dedicated IT teams and infrastructure, K-12 schools often face different constraints. Many schools have limited IT staffing focused on maintaining core systems like student information databases, learning management platforms, and device fleets for hundreds or thousands of students. +One of the primary challenges in high school districts is the varying levels of technical infrastructure and IT support available for educational technology deployments. While solutions like JupyterHub provide powerful multi-user environments that work well in university settings with dedicated IT teams and infrastructure, K-12 schools often face different constraints. Many schools have limited IT staffing focused on maintaining core systems such as student information databases, learning management platforms, and device fleets, for hundreds or thousands of students. -Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance – investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when especially when students are assigned a standard set of hardware and must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning is emphasised. +Setting up and maintaining a JupyterHub deployment requires dedicated resources for server management, user authentication, storage allocation, and ongoing maintenance – investments that are more readily available at the university level. For K-12 contexts, these operational requirements can be challenging to sustain, especially when balancing budgets across multiple competing priorities. Additionally, ensuring that every student has consistent access to the necessary software and libraries becomes more complex when managing server-based deployments, particularly when students are assigned a standard set of hardware. They must be configured identically across a district. Lastly, the flexibility and power of JupyterHub to manage one's own server infrastructure is a double-edged sword that is not always necessary or desirable in K-12 settings, where teaching and learning are emphasised. We identify four key challenges that Jupyter Everywhere aims to address specifically for K-12 environments: - Ensuring accessibility and ease of use for students and educators with varying levels of technical expertise. - Simplifying the setup and maintenance of Jupyter environments, reducing burdens on school IT staffing. - Prioritising security and privacy, especially when dealing with minors in educational settings, ensuring compliance with regulations such as the COPPA (Children's Online Privacy Protection Act), FERPA (Family Educational Rights and Privacy Act), and the SOPIPA (Student Online Personal Information Protection Act of the state of California). This includes safeguarding student data and ensuring secure access to educational resources. -- The lack of a database to store user data and notebooks grouped by accounts, such as by OAuth providers like Google or Microsoft. This limitation can be circumvented by using alternative methods for notebook sharing and distribution, which we discuss later in this post. +- The lack of a database to store user data and notebooks grouped by accounts, such as by OAuth providers like Google or Microsoft. This limitation can be circumvented by using alternative methods for sharing and distributing notebooks, which we discuss later in this post. ## The story of Jupyter Everywhere @@ -60,11 +60,11 @@ Here, we start by describing our journey building the application from the groun Jupyter Everywhere, as we mentioned earlier, is built on top of JupyterLite. JupyterLite is a distribution of JupyterLab that replaces Jupyter's server-side components with standards for in-browser communication with language kernels either in JavaScript or compiled to WebAssembly (WASM), shims for server-side Jupyter APIs, and in-browser file systems for storing user content and settings. -Initially, we started developing the application as a JupyterLab extension to prototype the functionality we wanted to build first: interacting with the file system and downloading notebooks in various formats. This enabled quick creation of the proof of concept, without the need to recompile JupyterLite during the initial iterations. Once we had a clearer idea of the UI and user workflows that needed to be implemented, we switched to a JupyterLite application. +Initially, we started developing the application as a JupyterLab extension to prototype the functionality we wanted to build first: interacting with the file system and downloading notebooks in various formats. This enabled the quick creation of the proof of concept, without the need to recompile JupyterLite during the initial iterations. Once we had a clearer idea of the UI and user workflows that needed to be implemented, we switched to a JupyterLite application. ### Facilitating notebook sharing and distribution -Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service provides API endpoints to authenticate and refresh tokens, and to upload notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as `.ipynb` files, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. +Jupyter Everywhere aims to simplify the sharing and distribution of notebooks among students and educators. Given the lack of a backend database to store user data and notebooks grouped by accounts, we had to devise alternative methods for notebook sharing. To this end, our collaborators at CourseKata developed a sharing service that supports authentication via JWT (JSON Web Tokens). This sharing service provides API endpoints for authenticating and refreshing tokens, as well as uploading notebooks to a server. The server is connected to the Jupyter Everywhere frontend via a REST API. The notebook contents are stored in an AWS S3 bucket, with appropriate security measures to ensure that only authenticated users can upload and access notebooks. The database is a managed PostgreSQL instance that stores notebooks as `.ipynb` files, with sharing-related metadata embedded as JSON fields in the notebook's "metadata" field. The sharing service provides two key features: - Uploading notebooks to a server for storage and sharing @@ -85,9 +85,9 @@ Later in the development process, we decided it would be more user-friendly to g There is some nuance to this: the view-only links are generated by hashing the notebook contents and session information, meaning that if the notebook is modified, the "Share" button needs to be interacted with again, or the user needs to wait until the next Jupyter auto-save occurs – which has a cadence of thirty seconds by default. This means that the shared link always points to the latest _saved_ version of the notebook, i.e., a snapshot of it. Users need to be aware that they may need to re-share the link if they make changes to the notebook after sharing it, or ask the recipient(s) to refresh the page to see the latest snapshot. -Once the link is generated, users can share the link with others on any platform, such as email, messaging apps, or social media. Recipients can then click the link to view the notebook in their web browser, without installing any additional software or creating an account. +Once the link is generated, users can share it with others on any platform, such as via email, messaging apps, or social media. Recipients can then click the link to view the notebook in their web browser, without installing any additional software or creating an account. -When a user opens a shared notebook link, they are presented with a read-only view of the notebook, where they can navigate through the cells, view outputs, and interact with any visualisations or widgets embedded in the notebook. However, they cannot modify the notebook's contents or execute any cells. +When a user opens a shared notebook link, they are presented with a read-only view of the notebook, where they can navigate through the cells, view outputs, and interact with any visualisations or widgets embedded in the notebook. However, they cannot modify the notebook's contents or execute any cells within it. This was trickier to implement than we initially thought. At that time, JupyterLab did not expose a command for opening a notebook without a kernel attached. Fortunately, Jupyter is a swiss-army knife of extensibility, and we were able to override JupyterLab's default behaviour to allow opening a notebook in read-only mode without a kernel. This involved creating a custom notebook factory that would create a read-only notebook widget, and overriding the default kernel selection behaviour to prevent the user from selecting a kernel for the read-only notebook – by not instantiating a kernel at all. @@ -118,7 +118,7 @@ Since downloading notebooks is only half the story, you might ask – how does o Remember that Jupyter Everywhere does not have user accounts or a backend database to store user data and notebooks grouped by accounts. The frontend runs in what is termed the "Simple Interface", or more technically, the "single-document mode" of JupyterLab, which we customise heavily by disabling and reimplementing Jupyter extensions to provide a more user-friendly experience. This means there is no visible file browser or file management interface in Jupyter Everywhere, to avoid unnecessary complexity. {/* link to simple interface docs in jupyter */} -Hence, users need to upload notebooks manually to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e., `/lab/` relative to the landing page. +Hence, users must manually notebooks to a JupyterLite instance before the Jupyter Everywhere session is instantiated. This introduces some challenges, as the main entry point for Jupyter Everywhere is a landing page that does not have access to the JupyterLite in-browser file system, since JupyterLite is not yet running at that point, and no internal plugins are loaded. JupyterLite only starts up once the user is redirected to the JupyterLite application URL, which resides one level deeper, i.e., `/lab/` relative to the landing page. This means that not only do we need to provide a way for users to upload notebooks to JupyterLite before it starts up, but we also need to ensure that the uploaded notebooks are available in the user's Jupyter Everywhere session. @@ -127,7 +127,7 @@ We achieved this by adding an "Upload a Notebook" button that lets users select Here is a quick walkthrough of how it works behind the scenes: {/* add pictures */} -When the user clicks the "Upload a Notebook" button, a file input dialog is opened, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. +When the user clicks the "Upload a Notebook" button, a file input dialog opens, allowing the user to select a local IPyNB file from their computer. Once the file is selected and uploaded, we use the Web Storage API, to store the contents of the uploaded notebook in `localStorage` without interacting with the sharing service or requiring the user's explicit consent. We then redirect the user away from the Jupyter Everywhere landing page and to the JupyterLite application URL, appending a query parameter to the UUID of the uploaded notebook. This query parameter is then parsed by Jupyter Everywhere upon startup. The notebook is read from `localStorage` and written to the JupyterLite in-browser file system, allowing the user to open and interact with the uploaded notebook as if it were created in Jupyter Everywhere itself. The notebook is then removed from `localStorage` to free up space, and no race conditions occur because the notebook is read only once during startup. @@ -143,17 +143,17 @@ Jupyter Everywhere's design called for a simplified interface, with the default The files are displayed in a grid, akin to a file browser on phones and desktop computers, with each file shown as a tile containing a placeholder and its associated filename. -After starting with bare-bones functionality, we added the ability to delete the uploaded files and download them back to local computers. Another requirement that came up was the ability to rename files, as users might want to organise their data files better. The design that we settled on was adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands on clicking. +After starting with bare-bones functionality, we added the ability to delete the uploaded files and to download them back to local computers. Another requirement that arose was the ability to rename files, as users might want to organise their data files more effectively. The design that we settled on was adding an "ellipsis" menu to each file tile, allowing users to perform actions such as renaming, deleting, and downloading files via a context menu that expands when clicked. The Files widget is accessible via a sidebar tab, allowing users to easily switch between their notebook and the Files widget. This design choice ensures that users can manage and visualise their data files without cluttering the main notebook interface, with the files stored in the same in-browser file system as the notebooks themselves. It uses JupyterLite's `ContentsManager` API, which abstracts away file system operations regardless of the underlying storage mechanism. #### The quest for being able to render uploaded files within the notebook interface -While the files could now be uploaded and accessed from the code cells, we ran into a quirk: uploaded images would not render correctly when embedded in Markdown cells within the notebook. We found that JupyterLite does not actually serve the user-uploaded files from local storage. After discussing the problem with other JupyterLite maintainers, we confirmed that there are two ways to solve it: serve the files from a Web Worker thread, or base64-encode the image contents during rendering (as we initially proposed). Due to compatibility concerns with Web Workers, we settled on a base64-encoding approach. +While the files could now be uploaded and accessed from the code cells, we ran into a quirk: uploaded images would not render correctly when embedded in Markdown cells within the notebook. We found that JupyterLite does not actually serve the user-uploaded files from local storage. After discussing the problem with other JupyterLite maintainers, we confirmed that there are two ways to solve it: serving the files from a Web Worker thread, or base64-encoding the image contents during rendering (as we initially proposed). Due to compatibility concerns with Web Workers, we settled on a base64-encoding approach. -To `base64` encode images in Markdown cells, we needed to selectively inject our custom logic into the rendering pipeline. The base64-encoded images can be provided to the browser using the `data:` scheme which defines a ["data URL"](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). -Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface – we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our own in order to provide data URLs for user-uploaded images in JupyterLite. -To that end, [we replaced the usage of the hard-coded UrlResolver component in JupyterLab][PR17784] with a dependency on a new plugin that would create (manufacture) URL resolvers. Such a refactor allowed us to [swap the URL resolver implementation in JupyterLite][PR1707] for one which resolved the local links to user-uploaded images to data URLs, enabling rendering of files from the in-browser file system. +To `base64` encode images in Markdown cells, we needed to selectively inject our custom logic into the rendering pipeline. The base64-encoded images can be provided to the browser using the `data:` scheme, which defines a ["data URL"](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). +Serendipitously, JupyterLab already had a concept of an URL resolver for renders, specified by the [`RenderMimeRegistry.IResolver`][IResolver] interface – we only needed a way to swap the default [`RenderMimeRegistry.UrlResolver`][UrlResolver] implementation for our to provide data URLs for user-uploaded images in JupyterLite. +To that end, [we replaced the usage of the hard-coded UrlResolver component in JupyterLab][PR17784] with a dependency on a new plugin that would create (manufacture) URL resolvers. Such a refactor allowed us to [swap the URL resolver implementation in JupyterLite][PR1707] for one that resolved the local links to user-uploaded images to data URLs, enabling the rendering of files from the in-browser file system. [IResolver]: https://jupyterlab.readthedocs.io/en/stable/api/interfaces/rendermime.IRenderMime.IResolver.html [UrlResolver]: https://jupyterlab.readthedocs.io/en/stable/api/classes/rendermime.RenderMimeRegistry.UrlResolver.html @@ -185,7 +185,7 @@ For the Pyodide kernel, we implemented a Jupyter Everywhere plugin that calls `p Popularised by platforms such as [Observable](https://observablehq.com/), Google Colaboratory, Kaggle Notebooks, and Deepnote, "Run" buttons next to code cells are a common feature in modern interactive computing environments. These buttons provide a convenient way for users to execute individual code cells with a single click, without needing to navigate to the toolbar or use keyboard shortcuts. -Unfortunately, however, Jupyter does not provide this functionality out of the box. This is a long-standing feature request in the Jupyter community, with various discussions on how to implement it effectively and in a manner that aligns with Jupyter's design principles, user experience, and accessibility standards: https://github.com/jupyterlab/jupyterlab/issues/2109 +Unfortunately, however, Jupyter does not provide this functionality out of the box. This is a long-standing feature request in the Jupyter community, with various discussions on how to implement it effectively and in a manner that aligns with Jupyter's design principles, user experience, and accessibility standards: https://github.com/jupyterlab/jupyterlab/issues/2109. Building on prior art that has explored this feature in PRs X and Y (TODO link them), we implemented a plugin that adds "Run" buttons next to each code cell in the notebook interface. This involves creating a factory that produces a button widget in the input area of each code cell. The button is styled as an orange circular button with a white "play" icon. It is positioned to the left of the code cell input area. @@ -195,25 +195,25 @@ When the user clicks the "Run" button, it triggers the execution of the correspo ### The dance of URL parameters -We use URL parameters extensively in Jupyter Everywhere to extend the plugins and customise the user experience based on the context in which the application is launched. For instance, we use URL parameters to specify which notebook to open upon startup, whether to launch in view-only mode, and other configuration options that affect the behaviour of the application. +We use URL parameters extensively in Jupyter Everywhere to extend the plugins and customise the user experience based on the context in which the application is launched. For instance, we use URL parameters to specify which notebook to open upon startup, whether to launch in view-only mode, and other configuration options that affect the application's behaviour. -URL parameters are query strings appended to the URL after a question mark (`?`), consisting of key-value pairs separated by ampersands (`&`). They provide a method for passing information to web applications in a stateless manner, allowing for dynamic configuration without the need for server-side state management. +URL parameters are query strings appended to the URL after a question mark (`?`), consisting of key-value pairs separated by ampersands (`&`). They provide a method for passing information to web applications in a stateless manner, allowing for dynamic configuration without requiring server-side state management. Here is a description of some of the key URL parameters used in Jupyter Everywhere. We use these parameters to control various aspects of the application's behaviour and user experience, as described below: - `notebook`: Specifies the UUID of the notebook to open upon startup by calling the sharing service API. This parameter displays view-only notebooks if loaded, as the sharing service does not possess a token to authenticate the user. At the same time, we append this URL parameter as soon as the notebook is shared, so that users can share the URL directly if they wish to. - `uploaded-notebook`: Specifies the UUID of a notebook that has been uploaded via the "Upload a Notebook" button on the landing page. This parameter allows users to upload a local IPyNB file from their computer and have it opened automatically in Jupyter Everywhere upon startup. This URL parameter is removed immediately after the notebook is read from `localStorage` and written to the JupyterLite in-browser file system. -- `kernel`: Specifies the kernel to use for executing code cells in the notebook. This parameter allows users to select between `python` (Python/Pyodide) and `r` (xeus-r/R) kernels, and the default is `python`, so if the parameter is omitted, the Pyodide kernel is used. This URL parameter is also appended to the URL when the user selects a different kernel from the kernel selection dropdown in the notebook toolbar, and is immediately removed as it is strictly an implementation detail. For uploaded notebooks, we utilise the notebook's metadata to determine the kernel to use, and pass that information via this URL parameter during startup. -- `tab`: Specifies which sidebar tab to open upon startup. This parameter allows users to choose between the "Files" (`files`) tab and the "Notebook" tab, with the default being the "Notebook" tab. This URL parameter is also appended to the URL when the user switches between the sidebar tabs, allowing for deep linking to specific tabs within the application. We also use this parameter to provide a `404` tab, which redirects to a custom "Not found" page if the specified resource does not exist. +- `kernel`: Specifies the kernel to use for executing code cells in the notebook. This parameter allows users to select between `python` (Python/Pyodide) and `r` (xeus-r/R) kernels. The default is `python`, so if the parameter is omitted, the Pyodide kernel is used. This URL parameter is also appended to the URL when the user selects a different kernel from the kernel selection dropdown in the notebook toolbar, and is immediately removed as it is strictly an implementation detail. For uploaded notebooks, we utilise the notebook's metadata to determine the kernel to use, and pass that information via this URL parameter during startup. +- `tab`: Specifies which sidebar tab to open upon startup. This parameter allows users to choose between the "Files" (`files`) tab and the "Notebook" tab, with the default being the "Notebook" tab. This URL parameter is also appended to the URL when the user switches between the sidebar tabs. We also use this parameter to provide a `404` tab, which redirects to a custom "Not found" page if the specified resource does not exist. ### User experience (UX) and data safety utilities -User experience is built into the core philosophy and technical design of Jupyter Everywhere. As discussed, our target audience is high school students, many of whom may be new to programming and interactive computing, which led to us having prioritised creating an intuitive and user-friendly interface that minimises cognitive load and maximises engagement, while emphasising aspects during the development cycle that enhance usability and accessibility. This reflects in how we handle data safety and loss prevention, given that Jupyter Everywhere works without user accounts or persistent storage. Read on to find out how we improvised such features in cool ways! +User experience is built into the core philosophy and technical design of Jupyter Everywhere. As discussed, our target audience is high school students, many of whom may be new to programming and interactive computing. This led us having to prioritise creating an intuitive and user-friendly interface that minimises cognitive load and maximises engagement, while emphasising aspects during the development cycle that enhance usability and accessibility. This is reflected in how we handle data safety and loss prevention, given that Jupyter Everywhere operates without user accounts or persistent storage. Read on to find out how we improvised such features in cool ways! #### Leave confirmation dialogs -Jupyter Everywhere uses the [`memoryStorageDriver](https://jupyterlite.readthedocs.io/en/stable/howto/configure/storage.html#local-storage-drivers) by default, which stores user data in the browser's memory in an ephemeral manner. This means that when the user closes the browser tab or window, all their data can be lost. To mitigate this risk, we iterated on the user experience on various occasions to add various utilities that help students avoid losing their work inadvertently, since we decided not to use an account-based system for storing user data and notebooks. +Jupyter Everywhere uses the [`memoryStorageDriver](https://jupyterlite.readthedocs.io/en/stable/howto/configure/storage.html#local-storage-drivers) by default, which stores user data in the browser's memory in an ephemeral manner. This means that when the user closes the browser tab or window, all their data can be lost. To mitigate this risk, we iterated on the user experience on various occasions to add various utilities that help students avoid inadvertently losing their work, as we decided not to use an account-based system for storing user data and notebooks. The first of these is a confirmation dialog that appears when the user attempts to close the browser tab or window while there are unsaved changes in the notebook. This dialog prompts the user to confirm their intention to leave the page, giving them a chance to save their work before exiting. @@ -221,21 +221,21 @@ The first of these is a confirmation dialog that appears when the user attempts #### Auto-save and continuous backup, and keeping the notebook URL parameter up to date -Additionally, Jupyter provides an auto-save feature that automatically saves the notebook at a regular interval (every thirty seconds by default). We have connected the save event to the sharing service, so that whenever the notebook is auto-saved, the sharing service is called to upload the latest version of the notebook to the server. This ensures that the user's work is continuously backed up, reducing the risk of data loss due to unexpected closures or crashes, accidental navigation away from the page, or the act of forgetting to save manually. +Additionally, Jupyter provides an auto-save feature that automatically saves the notebook at regular intervals (every thirty seconds by default). We have connected the save event to the sharing service, so that whenever the notebook is auto-saved, the sharing service is called to upload the latest version of the notebook to the server. This ensures that the user's work is continuously backed up, reducing the risk of data loss due to unexpected closures or crashes, accidental navigation away from the page, or forgetting to save manually. -Here, we append the `notebook` URL parameter to the URL whenever the notebook is auto-saved, ensuring that the URL always points to the latest saved version of the notebook. If the user accidentally closes the tab or window, or reloads, Jupyter Everywhere will open and they can continue where they left off by creating a copy of the latest saved notebook contents retried from the sharing service. +Here, we append the `notebook` URL parameter to the URL whenever the notebook is auto-saved, ensuring that the URL always points to the latest saved version of the notebook. If the user accidentally closes the tab or window and returns to Jupyter Everywhere, or reloads the page, they can continue where they left off by creating a copy of the latest saved notebook contents retrieved from the sharing service. #### Toast notifications for save reminders and kernel switches -JupyterLab provides a built-in notification system that allows extensions to display messages to users in a non-intrusive manner on the bottom-right corner. After many discussions on the topic, we have carefully utilised this functionality to keep users informed of the state of their notebooks. We display a toast notification for save reminders. That is, when the user makes changes to the notebook and has not copied the sharing link yet, we remind them to save their work by clicking the "Share" button and copying the link. This notification appears on "dirty" events, i.e., when the notebook has unsaved changes, and disappears once the user clicks the "Share" button. The timing and frequency of these notifications have been carefully calibrated to avoid overwhelming the user while still providing timely reminders. We use a five-minute interval between such notifications, which we found to be a good balance between being helpful and not intrusive. +JupyterLab provides a built-in notification system that allows extensions to display messages to users in a non-intrusive manner in the bottom-right corner. After many discussions on the topic, we have carefully utilised this functionality to keep users informed of the state of their notebooks. We display a toast notification for "save reminders". That is, when the user makes changes to the notebook and has not yet copied the sharing link, we remind them to save their work by clicking the "Share" button and copying the link. This notification appears on "dirty" events, i.e., when the notebook has unsaved changes, and disappears once the user clicks the "Share" button. The timing and frequency of these notifications have been carefully calibrated to avoid overwhelming the user while still providing timely reminders. We use a five-minute interval between such notifications, which we found to be a good balance between being helpful and not intrusive. -Similarly, we display toast notifications when the user switches kernels. For instance, if the user switches from the Pyodide kernel to the xeus-r kernel, we display a notification informing them that the kernel has been switched and that they may need to rerun the notebook for the changes to take effect. Jupyter uses non-reactive kernels, and hence does not automatically rerun the notebook when the kernel is switched, as this could lead to unexpected behaviour and data loss. Students are not aware of such nuances, especially when working with notebooks for the first time. Therefore, we found it important to inform them about this behaviour such that they can appropriately start writing code in a different programming language and note that the previous code cells will not have been executed in the new kernel, and that they may need to rerun the notebook from the start to ensure that all dependencies and variables are correctly defined in the new kernel, including different outputs and visualisations. +Similarly, we display toast notifications when the user switches kernels. For instance, if the user switches from the Pyodide kernel to the xeus-r kernel, we display a notification informing them that the kernel has been switched and that they may need to rerun the notebook for the changes to take effect. Jupyter uses non-reactive kernels, and hence does not automatically rerun the notebook when the kernel is switched, as this could lead to unexpected behaviour and data loss. Students are not aware of such nuances, especially when working with notebooks for the first time. Therefore, we found it essential to inform them about this behaviour such that they can appropriately start writing code in a different programming language and note that the previous code cells will not have been executed in the changed kernel, and that they may need to rerun the notebook from the start to ensure that all dependencies and variables are correctly defined in the changed kernel, including different outputs and visualisations. #### Leave warnings when there are uploaded files present -Browsers nowadays provide a built-in mechanism to warn users when they attempt to close a tab or window while there are unsaved changes in a form or input field. However, this mechanism does not cover all scenarios, especially when dealing with custom widgets and components within web applications, and has to be implemented manually using a [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) event listener. +Browsers nowadays provide a built-in mechanism to warn users when they attempt to close a tab or window while there are unsaved changes in a form or input field. However, this mechanism does not cover all scenarios, especially when dealing with custom widgets and components within web applications. It has to be implemented manually using a [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) event listener. -Particularly, we found that users might upload files to the Files widget without having any unsaved changes in the notebook itself. In such cases, the built-in browser mechanism would not trigger a warning when the user attempts to close the tab or window, potentially leading to data loss if the user forgets to download their uploaded files before exiting. We addressed this by connecting a `beforeunload` event listener to the Files widget's internal state, which tracks whether there is at least one uploaded file present. If so, the event listener now prompts the user with the browser's default message when they attempt to close the tab or window, thus serving as a reminder to ensure that they have fulfilled any necessary actions, such as downloading their uploaded files, before exiting. +Notably, we found that users might upload files to the Files widget without having any unsaved changes in the notebook itself. In such cases, the built-in browser mechanism would not trigger a warning when the user attempts to close the tab or window, potentially leading to data loss if the user forgets to download their uploaded files before exiting. We addressed this by connecting a `beforeunload` event listener to the Files widget's internal state, which tracks whether there is at least one uploaded file present. If so, the event listener now prompts the user with the browser's default message when they attempt to close the tab or window, thus serving as a reminder to ensure that they have fulfilled any necessary actions, such as downloading their uploaded files, before exiting. ## A call to action for educators and institutions