Verso 1.0 is out today, and the part I care most about isn't the notebook. It is the engine underneath it, a headless .NET library with no UI dependencies, of which the notebook is one consumer.
It is MIT licensed and published today as six NuGet packages plus a VS Code extension build attached to the GitHub release. You need the .NET 8 SDK.
What is in the box
| Category | Included at 1.0 |
|---|---|
| Kernels | C# (Roslyn), F# (FSharp.Compiler.Service), SQL (ADO.NET, provider-agnostic) |
| Cell rendering | Markdown via Markdig, SQL cells with paginated result tables |
| Data formatters | Primitives, collections, HTML, images, SVG, exceptions, F# types, SQL result sets |
| Themes | Verso Light, Verso Dark, Verso High Contrast (WCAG 2.1 AA) |
| Layouts | Notebook (linear), Dashboard (12-column CSS grid) |
| Magic commands | #!time, #!nuget, #!restart, #!about, #!import, and #!sql-connect, #!sql-disconnect, #!sql-schema, #!sql-scaffold from Verso.Ado |
| Toolbar actions | Run Cell, Run All, Clear Outputs, Restart Kernel, Switch Layout, Switch Theme, four exports (HTML, Markdown, CSV, JSON) |
| Serializers | .verso (JSON, read and write), .ipynb (import, nbformat v4 and later) |
| Front ends | VS Code extension, standalone Blazor Server web app |
| Packages | Verso, Verso.Abstractions, Verso.FSharp, Verso.Ado, Verso.Templates, Verso.Testing |
One engine, two front ends
The VS Code extension and the Blazor Server web app both sit on top of the engine, and they render the same components, so a notebook behaves the same in either place.
That split is why dotnet add package Verso makes sense in an application with no notebook in it at all. The engine is a library first.
Three kernels, one variable store
There is one variable store per session, and every kernel is handed the same one. There is no per-kernel isolation, so a value written by one language is readable by the next cell in another. You don't move it, and you don't name it twice.
// C# cell
var region = "us-east";
var threshold = 0.95;
Your top-level C# variables are published to the store after the cell runs. An F# cell reads them back through a typed helper the F# kernel injects into the session:
// F# cell
let region = tryGetVar<string> "region"
A SQL cell binds them as query parameters by name:
-- after #!sql-connect --name reporting --connection-string "..."
SELECT name, uptime FROM services
WHERE region = @region AND uptime < @threshold
The SQL kernel is provider-agnostic, so you bring the ADO.NET provider yourself with #r "nuget: ...". Verso.Ado also does named connections, schema inspection, and EF Core scaffolding from a live database through #!sql-scaffold.
A file you can read in a diff
The .verso format is JSON: a format identifier, notebook metadata, an ordered list of cells with stable ids, per-layout positioning, and outputs.
{
"verso": "1.0",
"metadata": {
"defaultKernel": "csharp",
"activeLayout": "notebook",
"preferredTheme": "verso-light"
},
"cells": [
{ "id": "...", "type": "code", "language": "csharp",
"source": "Console.WriteLine(\"Hello from Verso\");", "outputs": [] }
],
"layouts": {
"dashboard": { "cells": { "cell-id": { "row": 0, "col": 0, "width": 6, "height": 4 } } }
}
}
Those ids do more work than they look like. A cell keeps the same id across saves, so a reviewer can tell that a cell moved rather than that one disappeared and a similar one appeared elsewhere, and layout metadata points at cells by id rather than by position. Jupyter files come in through the .ipynb reader, nbformat v4 and later.
Ten interfaces, and our own features are behind them
Verso.Abstractions defines ten extension interfaces: ILanguageKernel, ICellRenderer, ICellType, IToolbarAction, IDataFormatter, IMagicCommand, ITheme, ILayoutEngine, INotebookSerializer, and INotebookPostProcessor. A supplementary IExtensionSettings sits alongside any of them. All inherit IExtension, which carries identity and two lifecycle hooks.
Every built-in in the table above is written on those interfaces. The C# kernel is an ILanguageKernel. Verso Dark is an ITheme. Dashboard is an ILayoutEngine. Verso.FSharp and Verso.Ado are ordinary extension packages that reference Verso.Abstractions and ship on NuGet the way yours would.
That is the second of the four rules we published this week, and the one with the sharpest edge. If one of our own features cannot be built on the public interfaces, the interfaces are what is wrong, and the interfaces are what we fix.
Getting it
dotnet add package Verso # the engine, in your own app
dotnet add package Verso.Abstractions # all an extension author needs
dotnet new install Verso.Templates
dotnet new verso-extension -n MyExtension
The VS Code extension is a .vsix on the GitHub release. The browser app runs from source with dotnet run --project src/Verso.Blazor.
What 1.0 does not have
No command line tool, so headless execution in CI means driving the engine yourself. No Python, PowerShell, JavaScript or HTTP cells. .ipynb is import only. Two layouts, and no way to browse or install an extension from inside the notebook.
The interfaces are the promise, and everything above them is what we keep adding to, one release at a time. Give it a try, and if you go to write an extension and the interface you need is missing something, that's the report I most want to see.