Boardflare Notebook Changelog

Release notes and API evolution.

This changelog records public API, protocol, and runtime contract changes across Boardflare releases.

Python for Sheets removed from the repository

What changed

  • Python for Sheets Removed: The Google Sheets add-on source is not in the Boardflare repository. It is archived at git tag sheets-archive. The published Google Workspace Marketplace Editor add-on remains available and frozen on its pinned deployments, and no further updates are planned.

October 2026 release (v1.4.1)

What this release ships

  • Workbook Tables: Declare editable tables with bf.table(...). Users add, edit, and delete rows in the table, and the notebook reads the current rows through .dataframe.
  • Several Tables per Notebook: A notebook can declare more than one table. Each table is keyed by its name, and declaring a table again with the same name replaces the older one.
  • Notebook Code Sheet Format Version 2: The _BOARDFLARE sheet records a workbook instance ID in cell H12. See Workbook Editing.

Details

  • bf.table Editor and .dataframe: Display the table as a cell’s last expression. Displaying it connects to the workbook and creates the table on its worksheet if the table is missing. .dataframe has _id first, then the declared columns in declaration order. A checkbox is True or False. Blank text, blank booleans, and extra columns are None, and blank number columns are NaN. A cell that reads .dataframe reruns when rows change, whether the change came from the editor or from the sheet. A read covers at most 100,000 cells (rows times columns, _id included). Adding a row in Excel requires ExcelApi 1.15.
  • Tables Need Their Editor Displayed: A table that is never displayed never connects, so a cell that reads its .dataframe waits until the editor connects.
  • Table Column Names: bf.table rejects column names that duplicate another column name regardless of case, and reserved column names.
  • Workbook Instance ID: The add-in writes a random UUID to H12 at the workbook’s first notebook save. It is used for analytics and is not written on open. Sheets at format version 1 load, and the next save writes format version 2.

Unreleased

  • Connector Reads through bf.inputs: bf.connector.microsoft_graph(scopes=[...]) declares delegated Microsoft Graph access, and graph.get(path, params=...) describes a GET read that bf.inputs(...) performs. See Workbook Design: Connector reads and bf.connector.
  • One Sign-in per Connector: Reads through the same connector declaration share one sign-in, started with Connect in the Workbook Inputs section. Until then, the cell stops with a sign-in message.
  • In-Memory Token: Microsoft Graph tokens are held in memory only, so reloading the add-in requires signing in again.

October 2026 release (v1.4.0)

What this release ships

  • Reactive Inputs & Selections: Read workbook inputs with bf.inputs(...), cell and table references with bf.ref(...), and capture active grid selections with bf.selection(...). See Workbook Design: Reading Workbook Inputs.
  • Pre-Bundled Packages: Import 15 curated companion packages directly in the zero-network runtime, with no dependency declarations (notebooks have no PEP 723 script block; see Imports and Dependencies). See Python Notebooks: Pre-bundled Companion Packages.
  • Worksheet Code Sheet & AI Edit Loop: Inspect, modify, and reload notebook code directly in Excel through the _BOARDFLARE worksheet and the AI edit handshake (increment cell H1, read cell H2). See Workbook Editing.
  • Google Sheets Add-in: Run reactive Marimo notebooks inside Google Sheets with workbook input synchronization via bf.inputs(), manual Refresh, and pre-bundled packages.

Details

  • bf.selection Table Selections: Active table selections can be tracked using bf.selection("TableName"), returning a pandas DataFrame with the table’s headers and at most one row: the row at the user’s active cell (the cell the cursor is on), even when a larger range is highlighted. The result is empty when the active cell is outside the table’s body rows or columns, on another sheet, on the header or total row, or on a hidden or filtered row. Rows carry values rather than positions. Limits: the cursor must be on a table cell, because cells beside the table select no row; filtering that hides the active row does not refresh the value until the next selection event.
  • One bf.inputs per Notebook: Enforced at most one bf.inputs(...) widget per notebook. Declaring more than one raises RuntimeError("Only one bf.inputs(...) is allowed per notebook; use one bf.inputs(...) with all inputs").
  • Notebook Source on Code Sheet: Notebook code is stored in the hidden _BOARDFLARE worksheet (format version 1), allowing chat assistants (Copilot, ChatGPT, Claude) and external tools to edit code and workbook data together. Template provenance (templateId and templateVersion) is stored directly in cells H10:H11. Excel workbooks with a Custom XML notebook migrate on opening: the source is written to the code sheet, read back, and only then is the Custom XML part removed. The task pane has no built-in AI editor; notebook code is edited directly on the code sheet.
  • Zero-Network Execution Sandbox: Notebook code has no direct external network access. Dynamic package installation over the network is disabled; dependencies are served from Boardflare via pre-bundled companion packages in the Pyodide lockfile, and notebooks cannot make external HTTP requests.
  • Blank header cells are None: When reading ranges with headers=True, blank header cells are None, not the string "nan". This matches Python in Excel (xl()) behaviour and uses pd.Index(..., dtype=object).
  • Duplicate headers: Range inputs containing duplicate column header labels are handled by normalizing columns by position index.
  • Publication Value Property Raises RuntimeError: Accessing .value on a bf.publish(...) result raises RuntimeError pointing authors to publication.consumers.
  • Timezone-aware datetime error: Publishing a timezone-aware datetime raises an actionable error message explaining that Excel stores wall-clock values and providing a concrete conversion snippet (value.astimezone(zone).replace(tzinfo=None)).