From 438b380d04eb2c755431193b1b17e40c11c41cae Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 10 Feb 2026 20:26:05 -0600 Subject: [PATCH] Fix broken "Cell Output Must Be at the Top Level" example in notebook patterns The previous "FIXED" example still had mo.md() called as a side effect inside if/else blocks (never rendered) while mixing it with a DataFrame in the same cell. Replace with the correct pattern: split into separate cells where each displays exactly one thing at the top level. --- docs/notebook-patterns.md | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/notebook-patterns.md b/docs/notebook-patterns.md index e1777e2..17ee1ed 100644 --- a/docs/notebook-patterns.md +++ b/docs/notebook-patterns.md @@ -83,7 +83,7 @@ def fetch_details(client, DATAINDEX, results): Marimo only renders the **last expression at the top level** of a cell as rich output. An expression buried inside an `if`/`else`, `for`, `try`, or any other block is **not** displayed — it's silently discarded. -**BROKEN** — `_df` inside the `if` branch is never rendered: +**BROKEN** — `_df` inside the `if` branch is never rendered, and `mo.md()` inside `if`/`else` is also discarded: ```python @app.cell @@ -93,26 +93,35 @@ def show_results(results, mo): mo.md(f"**Found {len(results)} results**") _df # Inside an if block — marimo does NOT display this else: - mo.md("**No results found**") + mo.md("**No results found**") # Also inside a block — NOT displayed return ``` -**FIXED** — assign inside the branches, display at the top level: +**FIXED** — split into separate cells. Each cell displays exactly **one thing** at the top level: ```python +# Cell 1: build the data, return it @app.cell -def show_results(results, mo): - _output = None - if results: - _output = pl.DataFrame(results) - mo.md(f"**Found {len(results)} results**") - else: - mo.md("**No results found**") - _output # Top-level last expression — marimo renders this - return +def build_results(results, pl): + results_df = pl.DataFrame(results) if results else None + return (results_df,) + +# Cell 2: heading — mo.md() is the top-level expression (use ternary for conditional text) +@app.cell +def show_results_heading(results_df, mo): + mo.md(f"**Found {len(results_df)} results**" if results_df is not None else "**No results found**") + +# Cell 3: table — DataFrame is the top-level expression +@app.cell +def show_results_table(results_df): + results_df # Top-level expression — marimo renders this as interactive table ``` -**Rule of thumb:** initialize a `_output = None` variable before any conditional, assign the displayable value inside the branches, then put `_output` as the last top-level expression. When it's `None` (e.g., the `else` path), marimo shows nothing — which is fine since the `mo.md()` already provides feedback. +**Rules:** +- Each cell should display **one thing** — either `mo.md()` OR a DataFrame, never both +- `mo.md()` must be a **top-level expression**, not inside `if`/`else`/`for`/`try` blocks +- Build conditional text using variables or ternary expressions, then call `mo.md(_text)` at the top level +- For DataFrames, use a standalone display cell: `def show_table(df): df` ### Async Cells