Changelog
v2.0.0-beta.8 (2026-11-03)
JupyterLab viewer
- A source table whose rows exceed
preview.max_bytesbefore reaching its row limit, for instance 20000 FHIR diagnoses with many text columns, shows the first rows that fit and limit reached instead of failing withSource query page exceeds the byte limit. Its related tables then follow the rows it shows.
v2.0.0-beta.7 (2026-11-02)
JupyterLab viewer
- A recipe can read
store[key].columnsin itsmainto adapt to the tables picked for it, for instance to relate the stays toperson.person_idin an OMOP database and topatient.idin a FHIR one. Each table reads its column names once, on the query thread of its connection.
v2.0.0-beta.6 (2026-11-01)
JupyterLab viewer
- Ctrl+Z undoes a change made with a button that loses the focus, such as Delete in the span toolbar, and changes made after a click in Safari or Firefox on macOS, which do not focus clicked buttons: the shortcut then goes to the widget clicked last instead of being lost.
- After the Metanno kernel is restarted from another JupyterLab window or by the server, the sidebar lists its sources again in a few seconds instead of waiting two minutes for the requests sent to the stopped kernel.
- A table field that no row has yet, for instance a score no one has filled, shows its key as column name instead of an empty header.
- Views always come from the nearest
metanno-homeabove the kernel folder: annotation Stores no longer save its path, so a.pretfile also reopens on another machine or account, andview_openerno longer takesconfig_path. - Query source shows the rows of its table as soon as its own query returns, while its related tables show Loading related data until theirs return, so a large related table (for instance the notes of selected entities) no longer hides the queried rows for minutes.
- A related table without the column of its relation, for instance notes without
note_idnext to TNM entities, shows which column is missing instead ofnull literals require is_null or is_not_null. - Recipes, the Python files of views, now go in
metanno-home/recipes, while saved views stay inmetanno-home/views. Recipes already inviewsstill load, but one of the same name inrecipesreplaces them. - The
tablerecipe shows each table of a view or annotation in its own tab, with a form for the rows selected in the last table used. Since it is offered for any view or annotation with tables, it also shows the raw tables of a view built with another recipe. - A recipe can name the tables it reads by annotating the
storeparameter of itsmainwith aTypedDict, whose keys can carry a description and keywords:notes: Annotated[DataFrame, RecipeTable("Clinical notes", keywords=["note", "doc"])]. Metanno then checks them before running the recipe, and the recipe shows in a new Recipes section of the sidebar. - Opening a recipe from the sidebar, or choosing in a tab a recipe that reads tables the view does not have, shows a table picker for each of them. Tables matching the keywords come first, then those next to the tables already chosen, e.g.
xxx.note_tnmoncexxx.noteis chosen. The choices are the tables listed so far in the sidebar, and those listed later, for instance after a Trino login, join them without reopening the tab. - A
RecipeTablecan setmax_rows, the rows loaded for its table instead ofpreview.rows, e.g.RecipeTable(max_rows=50)for the patients andRecipeTable(max_rows=5000)for their stays to show few patients with all their stays. A view or saved view that setsmax_rowsfor a table keeps its own. - All, Patient and Stay above a related table, one per table above it with a selected row, choose whose rows it shows, e.g. the notes of the selected patient or of their selected stay, replacing the Selected ... only checkbox. While no stay is selected, only All and Patient show. Clicking a note selects and scrolls to its stay and its patient, even of another patient than the listed stays, while the notes shown stay until Stay is clicked. A text widget in
"all"mode shows the documents of the nearest selected ancestor the same way. The names come fromparent_row_name, e.g."patient"given by the stays table. - Choosing a table in the pickers of a recipe fills the empty ones with the tables of the same database named as their key or one of its keywords, e.g.
cse.visit_occurrenceoncepersoniscse.person. Picking another table moves the tables filled this way to its database. - The table pickers of a recipe also show a field for each parameter of its
main, from its type, default and docstring description, checked with pydantic before the view opens. A parameter markedAnnotated[str, Column("entities")]lists the columns of the table picked forentities. Switching a view to a recipe with a required parameter shows the same form. - Save view in the toolbar of a source tab saves the open view, its recipe, tables and parameters, as
metanno-home/views/<name>.yml, which then shows under Explore. - The Connection selector of a tab also switches views with several tables: it offers the connections all their tables can use, e.g. Spark when DuckDB is too slow, and moves every table to the chosen one, since related tables are queried together. Save view then keeps that connection.
- Annotation files show as an
annotationssource, read only: each file is a folder of its tables, e.g.doc_classif/doctor1/docs, to compare annotators or show annotations next to database tables. Their rows are copied intodefault_connectionwhen a view opens, so views can relate them with the tables of DuckDB sources, or into an in-memory DuckDB without one.
Widgets
- Ctrl+Y also redoes the last change undone in a view, like Ctrl+Shift+Z.
create_table_widget(default_sort=["note_id", "begin"])orders the rows by these columns whenever the user has not sorted the table by a column header, e.g. the entities of each note in text order, or[("score", "desc")]for the highest scores of every row shown first. A header sort replaces it while active, and Clear sort brings it back.- Tables now show more of their column headers.
v2.0.0-beta.5 (2026-10-28)
JupyterLab viewer
- A table whose name contains a dot, for instance a
mycsv.csvdataset of a file source, shows its rows: its tab names itmycsv_csvin the Store, since widgets read a dot in a Store key as a nested path. - A file source whose
uriis only a scheme, e.g.s3a://, shows the buckets as its folders, its tables are named likemy_database/my_table.
v2.0.0-beta.4 (2026-10-27)
JupyterLab viewer
- File sources read CSV datasets with the connection's
read_csv: a folder whose data files are.csvor.csv.gzfiles, eg written by Spark, and a.csvfile next to folders.format: csvsets it for a whole source.
v2.0.0-beta.3 (2026-10-26)
JupyterLab viewer
- File sources show the folder tree under their
uriat any depth, local, mounted or remote, datasets are tables named by their path, egruns/notes. A folder holding data files and no subfolder is one dataset, also when it is theuriitself, and withoutformata table is Delta when its folder has a_delta_log. - Connections list HDFS and S3 folders without Spark with a
list_storage(uri)method returning(name, is_folder)pairs. - The sidebar settings button shows the
metanno-homefolder in the file browser and opensmetanno.yaml, in place of the views and annotations folder links. Every sidebar item, source, database, folder, table, view or annotation, is a row of the same height indented by its level, and open in a new tab shows on hover. - Other extensions open a dataset by its location with
metanno-viewer:open-resourceand{ kind: "path", uri }, read as a table of the source containing it or with the newdefault_connection.
v2.0.0-beta.2 (2026-10-22)
JupyterLab viewer
- Metanno files move to a
metanno-homefolder:metanno.yaml, connection modules,views/andannotations/. Theviews:key is removed. - Sources can list several
connections, switchable from the sidebar or a table tab. kind: databasessources list the databases of their connection, eg Trino schemas.- Connection factories can set
refresh_tables_on_view_opento read their tables again at each view opening. - Copy code imports connection modules without Metanno, and query code has no row limit.
- Rerunning an Open with Metanno cell refreshes its open panel instead of adding a sidebar entry.
- The explorer kernel only starts from the sidebar or Refresh, also after a shutdown.
- Each connection runs its queries on its own thread, so a slow database no longer blocks the others.
Widgets
- Bare text filters ignore case, while
contains,starts withandends withmatch case.
v2.0.0-beta.1 (2026-10-08)
Metanno 2 requires Pret 0.7.
JupyterLab viewer
view_opener(store)displays an Open with Metanno button that opens a Store in a JupyterLab tab, with the built in table view (a table and a form of the selected rows) or a view recipe.- View recipes are Python files of a views directory,
main(store, ...)returns the interface. Saved views (views/*.yml) select source tables, a recipe and its parameters. - The Metanno sidebar browses sources configured in
metanno.yaml(Ibis URLs or Python connection factories), saved views, and saved annotation Stores grouped by folder, eg one file per annotator. - Source tables open in tabs backed by a shared query kernel, with Copy code to customize the query in a notebook, as plain Ibis code opening the configured connection, or to prepare an editable annotation Store.
- Tabs have a recipe selector to show the same data with another recipe, a kernel selector, a reload button and fullscreen links. Annotations reopen in their saved kernel with their saved view, independently of the notebook that created them.
- Errors above views and widgets can be dismissed.
Widgets
DataWidgetFactorydisplays and edits Pret DataFrames as well as lists of dictionaries: paged tables with server side filtering and sorting, forms of the selected rows (long text fields included), text views and related tables linked throughDataRelation.- Tables of very large sources show their first 600,000 rows, which browsers can scroll, filters and sorting apply to all rows.
create_pdf_widgetshows the PDF of the selected row with the browser viewer.create_status_barshows rejected edits and undo controls of a Store,create_connection_status_barremains as an alias.- Fix
on_hover_spanscallback to trigger also when the cursor leaves a span while leaving its line
Other changes
DataWidgetFactory.create_filters_viewis removed, tables filter from their column headers.
v1.0.0 (2025-05-10)
- Added multi-document rendering to
DataWidgetFactory.create_text_widgetwithdocs_mode="lead" | "selected" | "all"and an optionalcontainer_rendererfor custom document wrappers (e.g. sticky headers) - Added text-widget imperative helpers to inspect and act on the active selection:
get_active_doc_id,get_selected_ranges,get_selected_text, andadd_span_from_selection. - Improved text-widget span workflows: selections are tracked per document, added spans get unique IDs, selected or highlighted hidden spans remain visible, and span scrolling now activates the span's document before scrolling.
- Improved default table, form, and text selections so linked widgets select the first available row/document on initial render.
- Improved
AnnotatedTextscrolling and layout: handle methods now accept scroll positioning options, and line spacing/gutter sizing uses shared CSS variables. - Improved the connection status bar with a visual pulse when synced writes are rejected while disconnected.
- Improved reliability thanks to pret v0.6.3
v1.0.0-beta.7 (2025-02-27)
- Added
label_formattertocreate_text_widgetto allow users to provide a function to customize the displayed label on top of text spans - Added function-based
optionsto let users dynamically choose options to display in the dropdown selectors (or radio) - Minor fixes to the data widget explorer and quaero demo
v1.0.0-beta.6 (2025-02-26)
New notebook-based tutorials : each tutorial can now be downloaded as a Jupyter notebook and run "at home".
New DataWidgetFactory !
- The
DatasetExplorerAppfactory moved tometanno/recipes/data_widget_factory.pyand is nowDataWidgetFactory create_table_widgetandcreate_form_widgetnow take fields built withinfer_fields(makes them reusable, or you can provide fields directly)create_text_widget:- now returns a toolbar widget and a text widget as a tuple
- now supports fields for the span editor to customize the toolbar
- Optional helpers can replace custom wiring
create_selected_field_viewfor simple value display of selected table rows, for instance to show a note headercreate_filters_viewfor table filters- Automatic syncing between tables, forms, and text widgets (no more manual callbacks required)
- Nested data support, ie
notes: [{"note_id": ..., "note_text": ..., "entities": [{"id": ..., "begin": ...}], ...}]can be seen in two views using store keysnotesandnotes.entities - New connection status bar to know if an app is connected, disconnected or offline.
New Pret changes !
- Transactions, connection status, store rollbacks, Jupyter "Open in a new tab" button, and more.
- Check out Pret's changelog for more info.
v1.0.0-beta.5 (2025-12-09)
- Table input suggestions are now more visible (with a blueish outline)
- Update
Tableprops and events: use pkey instead of row_key. Imperative handle now exposes scroll_to_row_idx and scroll_to_row_id. All row callbacks now receive row_id first, followed by row_idx, column, etc., and row_id can be None when unset. Adjust your handlers and highlighted-row keys to match. - Dataset explorer factory inputs (metanno/recipes/explorer.py):
- create_table_widget is keyword-only and uses the new Table API and handle (TableWidgetHandle with scroll/filter/highlight helpers). If you passed handlers, update them to the new signatures with row_id.
- New form widget: create_form_widget renders a single record with text, select, and boolean inputs.
- Text handle keys are renamed to set_doc_by_id, get_doc_id, scroll_to_span, set_highlighted_spans, and set_doc_by_id. New on_click_span callback added and hover callback now passes (span_ids, mod_keys).
- Added
begin_key,end_key,primary_key,style_key,label_keyandhighlighted_keyto allow the user to customize the provenance of these attributes in theAnnotatedTextcomponent span data. Breaking API change in
metanno.recipes.explorer:create_table_widget:pkey_column->primary_keyhidden_columns->hidden_keysid_columns->id_keyseditable_columns->editable_keyscategorical_columns->categorical_keysfirst_columns->first_keys
create_text_widget:text_pkey_column->text_primary_keyspans_pkey_column->text_primary_key- new
primary_key - new
begin_key - new
end_key - new
style_key - new
label_key - new
highlighted_key
- Minor documentation fixes.
v1.0.0-beta.4 (2025-11-26)
- Moved
pret.ui.metannotometanno.ui(accessible viafrom metanno import ...) - Renamed
actionsprops tohandleand manage them like true refs (i.e., users should access its content viahandle.current.xxx) - Added
on_mouse_hover_spansandon_mouse_hover_rowprops toAnnotatedTextandTablecomponents, which should offer a more reliable way to highlighted spans/rows on mouse hover - Refactored the
explorerrecipe andexamples/quaero.pyexample to use widgets instead of a complex/monolithic app
v1.0.0-beta.3 (2025-08-21)
- Fix AnnotatedText bug in text selection on desktop browsers
v1.0.0-beta.2 (2025-08-18)
- Improved visibility of highlighted spans and layout of boxes with mixed inset/outset nesting
- Improved autocompletion experience for table suggestion inputs
- Improved tokenization of text, now allowed to split around dashes (-).
- Added theoretical support for touch screens (lightly tested)
- Added tutorials
- Setup
- Run the Quaero Explorer app
- Customize the explorer
v1.0.0-beta.1 (2025-06-06)
- Complete refacto of the framework: the Python-JS synchronized app mechanisms have been moved to a new library: Pret !
- Text and Table components are now callable directly from python, along other components built around MUI Joy. This gives the user the ability to easily build simple apps, and iterate on the design step by step.
- Metanno now features new Image annotation component.
- The tests run in Python 3.7 - 3.10, using playwright in headless Firefox, Chrome and Webkit.
- Metanno now also has a nice interactive documentation !
Metanno v0
Metanno v0 is a JupyterLab extension for building custom annotation interfaces. It focuses on textual documents with structured entities and aims for modularity, customization, and interactivity. Core features include multi-line and nested span annotations, table-based relational annotations, multiple data types (hyperlinks, text, lists), and an immutable state that can be synchronized between the frontend and backend. The project compiles Python app classes to JavaScript using Transcrypt, enabling fast, browser-side execution while keeping Python code on the kernel side. To create an app, the user must modify one of the apps in recipes/*.py, and handle state management using a huge Redux-style reducer in select_state.
Last items in the v0 changelog:
- Almost complete typescript conversion
- Added column filters
- Customizable undo/redo logic
- Editable span annotations from a table view
- Accessible return value of a Python call from front end (async calls)