From 43aff6dee9692d371fa49221c4bcb99045d12639 Mon Sep 17 00:00:00 2001 From: krflol Date: Sun, 18 Oct 2026 11:40:55 -0500 Subject: [PATCH 1/3] Fix local Jupyter download URLs in a preserved notebook revision --- .gitattributes | 5 + .../bootstrap_components.ipynb | 330 ++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 examples/component-notebook-alpha6-r1/bootstrap_components.ipynb diff --git a/.gitattributes b/.gitattributes index de3dc4e..ce92280 100644 --- a/.gitattributes +++ b/.gitattributes @@ -152,3 +152,8 @@ examples/accessible-showcase-alpha6/fonts/*.ttf binary docs/assets/accessible-showcase-alpha6/** -text whitespace=cr-at-eol docs/assets/accessible-showcase-alpha6/*.pdf binary docs/assets/accessible-showcase-alpha6/fonts/*.ttf binary + +# The Jupyter download fix is a new notebook revision; old assets stay immutable. +examples/component-notebook-alpha6-r1/** text eol=lf +docs/assets/component-notebook-alpha6-r1/** -text +docs/assets/component-notebook-alpha6-r1/*.pdf binary diff --git a/examples/component-notebook-alpha6-r1/bootstrap_components.ipynb b/examples/component-notebook-alpha6-r1/bootstrap_components.ipynb new file mode 100644 index 0000000..0f75b7a --- /dev/null +++ b/examples/component-notebook-alpha6-r1/bootstrap_components.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "welcome", + "metadata": {}, + "source": [ + "# Make a PDF from Python components\n", + "\n", + "Edit an order, customize its cards and table, and export a PDF plus styled\n", + "and unstyled HTML. You write Python values and components; no HTML strings\n", + "are needed. All sample data is fictional.\n", + "\n", + "**In Colab:** choose **Runtime → Run all**, sign in if prompted, and use a\n", + "standard CPU runtime. No GPU or paid plan is needed for this example;\n", + "[Colab availability and limits](https://research.google.com/colaboratory/faq.html)\n", + "still apply. Runtime files are temporary, so download your results before\n", + "leaving. Local Jupyter also works.\n", + "\n", + "**Experimental:** this notebook pins Fullbleed **2.6.0a6**. The component API\n", + "may change and has documented CSS limits. Bootstrap components are static\n", + "in both PDF and HTML; no Bootstrap JavaScript is included." + ] + }, + { + "cell_type": "markdown", + "id": "install-help", + "metadata": {}, + "source": [ + "## 1. Install the tested alpha\n", + "\n", + "Run this before importing Fullbleed. If you already imported a different\n", + "version in this notebook session, restart its kernel/runtime first.\n", + "The wheel supplies the engine and font; Jupyter supplies display tools." + ] + }, + { + "cell_type": "code", + "id": "install-fullbleed", + "metadata": { + "tags": [ + "install-fullbleed" + ] + }, + "source": [ + "%pip install --quiet --only-binary=:all: \"fullbleed==2.6.0a6\"" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "data-help", + "metadata": {}, + "source": [ + "## 2. Make the order yours\n", + "\n", + "Change a name, quantity or price, then rerun this cell and the cells below it.\n", + "Prices are decimal strings; this example omits tax, shipping and discounts.\n", + "Try `items=[]` to see the empty state, or add rows to explore pagination." + ] + }, + { + "cell_type": "code", + "id": "order-data", + "metadata": {}, + "source": [ + "order = {\n", + " \"number\": \"1048\",\n", + " \"customer\": \"Moss & Fern Studio\",\n", + " \"collection\": \"The autumn collection\",\n", + " \"status\": \"Ready to ship\",\n", + " \"items\": [\n", + " {\"item\": \"Studio notebooks\", \"quantity\": 24, \"unit_price\": \"15.00\"},\n", + " {\"item\": \"Cotton stationery sets\", \"quantity\": 12, \"unit_price\": \"18.00\"},\n", + " {\"item\": \"Archival prints\", \"quantity\": 8, \"unit_price\": \"60.00\"},\n", + " ],\n", + "}" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "styles-help", + "metadata": {}, + "source": [ + "## 3. Choose the accent and type size\n", + "\n", + "These values feed ordinary Python styles. Later, `.with_parts(...)` styles\n", + "a card's own title and body. The custom `dispatch-note` class gets its rule\n", + "from `Document(styles=...)`; a class name alone does not create a style." + ] + }, + { + "cell_type": "code", + "id": "style-controls", + "metadata": {}, + "source": [ + "accent = \"#205b47\"\n", + "title_size = 30" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "components-help", + "metadata": {}, + "source": [ + "## 4. Compose reusable components\n", + "\n", + "`Panel` supplies reusable card defaults. `Metric` is an ordinary Python\n", + "function. `DataTable` builds its structure from rows and column functions.\n", + "Bootstrap classes such as `mb-4` come with the components; your custom\n", + "classes need their own rules. Text values are escaped automatically.\n", + "\n", + "A column callback can return a component: `Strong(row[\"item\"])` emphasizes\n", + "each item inside its table cell. Return text or compose other components\n", + "without constructing an HTML string.\n" + ] + }, + { + "cell_type": "code", + "id": "components", + "metadata": {}, + "source": [ + "from decimal import Decimal\n", + "from fullbleed.ui.experimental import Document, Page, Strong, Theme, mm, pt, style, styled\n", + "from fullbleed.ui.experimental import bootstrap as bs\n", + "\n", + "def money(value):\n", + " return f\"USD {value:,.2f}\"\n", + "\n", + "Panel = styled(bs.Card, border_color=\"#d7e1de\", border_radius=\"8px\")\n", + "\n", + "def Metric(value, label):\n", + " return Panel(\n", + " bs.Heading(value, level=2, class_name=\"mb-1\", style=style(color=accent)),\n", + " bs.Text(label, class_name=\"text-secondary\"),\n", + " )\n", + "\n", + "def OrderSummary(data):\n", + " rows = [dict(row, amount=Decimal(row[\"unit_price\"]) * row[\"quantity\"])\n", + " for row in data[\"items\"]]\n", + " total = sum((row[\"amount\"] for row in rows), Decimal(\"0.00\"))\n", + " units = sum(row[\"quantity\"] for row in rows)\n", + " details = bs.DataTable(\n", + " rows,\n", + " columns=[\n", + " bs.Column(\"Item\", lambda row: Strong(row[\"item\"])),\n", + " bs.Column(\"Quantity\", lambda row: row[\"quantity\"], align=\"right\"),\n", + " bs.Column(\"Amount\", lambda row: money(row[\"amount\"]), align=\"right\"),\n", + " ],\n", + " striped=True,\n", + " caption=\"Order contents · USD\",\n", + " ) if rows else bs.Alert(\"No items in this order.\", variant=\"light\")\n", + " return Document(\n", + " bs.Box(bs.Text(\"NORTHSTAR / STUDIO\", class_name=\"fw-bold\"),\n", + " bs.Badge(data[\"status\"], variant=\"light\", class_name=\"ms-3\"),\n", + " class_name=\"mb-4\"),\n", + " bs.Heading(data[\"collection\"], level=1,\n", + " style=style(font_size=pt(title_size), color=accent, line_height=1.1)),\n", + " bs.Paragraph(f'Prepared for {data[\"customer\"]}', class_name=\"lead mb-4\"),\n", + " bs.Row(\n", + " bs.Col(Metric(str(units), \"Items in this order\"), span=5),\n", + " bs.Col(Metric(money(total), \"Order total\"), span=7),\n", + " gutter=3, class_name=\"mb-4 mx-0\",\n", + " ),\n", + " Panel(details, title=f'Order {data[\"number\"]}',\n", + " footer=\"Made with care. Packed with purpose.\").with_parts(\n", + " title=style(color=accent, font_size=pt(16)),\n", + " body=style(padding=\"1.25rem\"),\n", + " ),\n", + " bs.Alert(\"Please retain this copy with your shipment.\",\n", + " class_name=\"dispatch-note mt-4\"),\n", + " title=f'Order {data[\"number\"]}',\n", + " page=Page(size=\"A4\", margin=mm(16)),\n", + " theme=Theme(text=style(font_family=\"Inter, Arial, sans-serif\", font_size=pt(10),\n", + " color=\"#18382f\", line_height=1.4)),\n", + " styles={\".dispatch-note\": style(color=accent, border_color=accent,\n", + " background_color=\"#f0f5f2\")},\n", + " )\n", + "\n", + "document = OrderSummary(order)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "render-help", + "metadata": {}, + "source": [ + "## 5. Render and export\n", + "\n", + "The PDF renderer receives the bundled Inter font file explicitly. A CSS\n", + "family name does not load a font. Styled HTML keeps the family declaration;\n", + "its browser host supplies Inter separately, or uses the Arial/sans-serif fallback.\n", + "\n", + "`include_styles=False` removes generated and inline styling but keeps classes\n", + "for a host application's CSS. Both export modes also work on an individual\n", + "component, such as `Metric(\"44\", \"Items\").to_html(include_styles=False)`.\n", + "Styled Bootstrap fragments include global Bootstrap rules; use unstyled\n", + "fragments when the host page already provides its stylesheet." + ] + }, + { + "cell_type": "code", + "id": "render-export", + "metadata": {}, + "source": [ + "from importlib.resources import files\n", + "from pathlib import Path\n", + "from fullbleed.ui.experimental import Renderer\n", + "\n", + "output = Path(\"output\")\n", + "output.mkdir(exist_ok=True)\n", + "font = files(\"fullbleed_assets\").joinpath(\"fonts/Inter-Variable.ttf\")\n", + "renderer = Renderer(font_files=[str(font)],\n", + " document_timestamp=\"2026-10-01T00:00:00Z\")\n", + "prepared = document.prepare(renderer)\n", + "pdf_path = output / \"order.pdf\"\n", + "pdf_path.write_bytes(prepared.render_pdf())\n", + "(output / \"order.styled.html\").write_text(\n", + " document.to_html(include_styles=True), encoding=\"utf-8\", newline=\"\\n\")\n", + "(output / \"order.unstyled.html\").write_text(\n", + " document.to_html(include_styles=False), encoding=\"utf-8\", newline=\"\\n\")\n", + "print(f\"Saved {pdf_path} and both HTML exports.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "preview", + "metadata": {}, + "source": [ + "from IPython.display import Image, display\n", + "\n", + "preview_paths = []\n", + "for number, png in enumerate(prepared.render_image_pages(dpi=110), start=1):\n", + " path = output / f\"order-page-{number}.png\"\n", + " path.write_bytes(png)\n", + " preview_paths.append(path)\n", + " display(Image(data=png, width=650))\n", + "print(f\"{len(preview_paths)} page(s). Review every page before using the document.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "download-help", + "metadata": {}, + "source": [ + "## 6. Download your files\n", + "\n", + "This ZIP contains the current PDF, both HTML exports, and every current page\n", + "preview. In Colab the next cell starts the download. In local Jupyter it\n", + "displays a link. The runtime's `output/` folder also contains the individual\n", + "files. If you edit the order or styles, rerun from that edit through this cell." + ] + }, + { + "cell_type": "code", + "id": "download", + "metadata": {}, + "source": [ + "import sys\n", + "from zipfile import ZipFile, ZIP_DEFLATED\n", + "\n", + "bundle_path = output / \"component-order.zip\"\n", + "current_files = [pdf_path, output / \"order.styled.html\",\n", + " output / \"order.unstyled.html\", *preview_paths]\n", + "with ZipFile(bundle_path, \"w\", compression=ZIP_DEFLATED) as bundle:\n", + " for path in current_files:\n", + " bundle.write(path, arcname=path.name)\n", + "\n", + "if \"google.colab\" in sys.modules:\n", + " from google.colab import files\n", + " files.download(str(bundle_path))\n", + "else:\n", + " from IPython.display import FileLink\n", + " display(FileLink(bundle_path.as_posix()))" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "next-steps", + "metadata": {}, + "source": [ + "## Keep composing\n", + "\n", + "- [Component guide and catalog](https://docs.fullbleed.dev/guides/bootstrap-components-preview/)\n", + " explain class names, named parts, fonts, static states and current limits.\n", + "- [Northstar and Common Ground source kits](https://docs.fullbleed.dev/guides/showcase-authoring/)\n", + " show a complete invoice and illustrated report in components and HTML/CSS.\n", + "- [The authored accessible guide](https://docs.fullbleed.dev/guides/accessible-showcase/)\n", + " includes the exact PDF, semantic source and scoped validation receipts.\n", + "- [Share an authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86)\n", + " if a composition or styling task feels awkward. Include the small component,\n", + " expected result and Fullbleed version; use fictional data in public posts.\n", + "- [Stable Python guide](https://docs.fullbleed.dev/guides/python-components/)\n", + " documents the established API separately from this alpha.\n", + "\n", + "The notebook and Fullbleed are MIT licensed. This example does not establish\n", + "complete Bootstrap CSS, browser, accessibility or PDF conformance." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "colab": { + "name": "bootstrap_components.ipynb", + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 95fba4fbd0f7965eb0e0ce62235eda5d547610ae Mon Sep 17 00:00:00 2001 From: krflol Date: Sun, 18 Oct 2026 11:44:55 -0500 Subject: [PATCH 2/3] Verify Jupyter editing and downloads and document local component authoring --- .github/workflows/docs.yml | 8 +- .../bootstrap_components.ipynb | 330 ++++++++++++++++++ .../browser-verification.json | 86 +++++ .../order-page-1.png | Bin 0 -> 78032 bytes .../component-notebook-alpha6-r1/order.pdf | Bin 0 -> 78744 bytes .../order.styled.html | 39 +++ .../order.unstyled.html | 1 + .../verification.json | 67 ++++ docs/getting-started/component-notebook.md | 76 +++- tools/build_component_notebook_samples.py | 20 +- tools/check_component_notebook_jupyter.py | 219 ++++++++++++ tools/check_component_notebook_ui.py | 10 +- ...omponent-notebook-browser-requirements.txt | 3 + tools/verify_component_notebook.py | 5 +- 14 files changed, 842 insertions(+), 22 deletions(-) create mode 100644 docs/assets/component-notebook-alpha6-r1/bootstrap_components.ipynb create mode 100644 docs/assets/component-notebook-alpha6-r1/browser-verification.json create mode 100644 docs/assets/component-notebook-alpha6-r1/order-page-1.png create mode 100644 docs/assets/component-notebook-alpha6-r1/order.pdf create mode 100644 docs/assets/component-notebook-alpha6-r1/order.styled.html create mode 100644 docs/assets/component-notebook-alpha6-r1/order.unstyled.html create mode 100644 docs/assets/component-notebook-alpha6-r1/verification.json create mode 100644 tools/check_component_notebook_jupyter.py create mode 100644 tools/component-notebook-browser-requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 74f3577..33cfecd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -77,7 +77,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - channel: [alpha3, alpha5, alpha6] + channel: [alpha3, alpha5, alpha6, alpha6-r1] steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 @@ -87,6 +87,12 @@ jobs: python -m pip install --only-binary=:all: -r tools/component-notebook-requirements.txt - name: Execute the notebook and check PDF, HTML and ZIP downloads run: python -I tools/verify_component_notebook.py --channel ${{ matrix.channel }} --check-downloads --out target/component-notebook + - name: Install the optional Jupyter browser test tools + if: matrix.channel == 'alpha6-r1' + run: python -m pip install --only-binary=:all: -r tools/component-notebook-browser-requirements.txt + - name: Edit, run and download in Jupyter Notebook + if: matrix.channel == 'alpha6-r1' + run: python -I tools/check_component_notebook_jupyter.py --reference target/component-notebook --out target/component-notebook/browser --browser chrome - uses: actions/upload-artifact@v4 if: always() with: diff --git a/docs/assets/component-notebook-alpha6-r1/bootstrap_components.ipynb b/docs/assets/component-notebook-alpha6-r1/bootstrap_components.ipynb new file mode 100644 index 0000000..0f75b7a --- /dev/null +++ b/docs/assets/component-notebook-alpha6-r1/bootstrap_components.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "welcome", + "metadata": {}, + "source": [ + "# Make a PDF from Python components\n", + "\n", + "Edit an order, customize its cards and table, and export a PDF plus styled\n", + "and unstyled HTML. You write Python values and components; no HTML strings\n", + "are needed. All sample data is fictional.\n", + "\n", + "**In Colab:** choose **Runtime → Run all**, sign in if prompted, and use a\n", + "standard CPU runtime. No GPU or paid plan is needed for this example;\n", + "[Colab availability and limits](https://research.google.com/colaboratory/faq.html)\n", + "still apply. Runtime files are temporary, so download your results before\n", + "leaving. Local Jupyter also works.\n", + "\n", + "**Experimental:** this notebook pins Fullbleed **2.6.0a6**. The component API\n", + "may change and has documented CSS limits. Bootstrap components are static\n", + "in both PDF and HTML; no Bootstrap JavaScript is included." + ] + }, + { + "cell_type": "markdown", + "id": "install-help", + "metadata": {}, + "source": [ + "## 1. Install the tested alpha\n", + "\n", + "Run this before importing Fullbleed. If you already imported a different\n", + "version in this notebook session, restart its kernel/runtime first.\n", + "The wheel supplies the engine and font; Jupyter supplies display tools." + ] + }, + { + "cell_type": "code", + "id": "install-fullbleed", + "metadata": { + "tags": [ + "install-fullbleed" + ] + }, + "source": [ + "%pip install --quiet --only-binary=:all: \"fullbleed==2.6.0a6\"" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "data-help", + "metadata": {}, + "source": [ + "## 2. Make the order yours\n", + "\n", + "Change a name, quantity or price, then rerun this cell and the cells below it.\n", + "Prices are decimal strings; this example omits tax, shipping and discounts.\n", + "Try `items=[]` to see the empty state, or add rows to explore pagination." + ] + }, + { + "cell_type": "code", + "id": "order-data", + "metadata": {}, + "source": [ + "order = {\n", + " \"number\": \"1048\",\n", + " \"customer\": \"Moss & Fern Studio\",\n", + " \"collection\": \"The autumn collection\",\n", + " \"status\": \"Ready to ship\",\n", + " \"items\": [\n", + " {\"item\": \"Studio notebooks\", \"quantity\": 24, \"unit_price\": \"15.00\"},\n", + " {\"item\": \"Cotton stationery sets\", \"quantity\": 12, \"unit_price\": \"18.00\"},\n", + " {\"item\": \"Archival prints\", \"quantity\": 8, \"unit_price\": \"60.00\"},\n", + " ],\n", + "}" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "styles-help", + "metadata": {}, + "source": [ + "## 3. Choose the accent and type size\n", + "\n", + "These values feed ordinary Python styles. Later, `.with_parts(...)` styles\n", + "a card's own title and body. The custom `dispatch-note` class gets its rule\n", + "from `Document(styles=...)`; a class name alone does not create a style." + ] + }, + { + "cell_type": "code", + "id": "style-controls", + "metadata": {}, + "source": [ + "accent = \"#205b47\"\n", + "title_size = 30" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "components-help", + "metadata": {}, + "source": [ + "## 4. Compose reusable components\n", + "\n", + "`Panel` supplies reusable card defaults. `Metric` is an ordinary Python\n", + "function. `DataTable` builds its structure from rows and column functions.\n", + "Bootstrap classes such as `mb-4` come with the components; your custom\n", + "classes need their own rules. Text values are escaped automatically.\n", + "\n", + "A column callback can return a component: `Strong(row[\"item\"])` emphasizes\n", + "each item inside its table cell. Return text or compose other components\n", + "without constructing an HTML string.\n" + ] + }, + { + "cell_type": "code", + "id": "components", + "metadata": {}, + "source": [ + "from decimal import Decimal\n", + "from fullbleed.ui.experimental import Document, Page, Strong, Theme, mm, pt, style, styled\n", + "from fullbleed.ui.experimental import bootstrap as bs\n", + "\n", + "def money(value):\n", + " return f\"USD {value:,.2f}\"\n", + "\n", + "Panel = styled(bs.Card, border_color=\"#d7e1de\", border_radius=\"8px\")\n", + "\n", + "def Metric(value, label):\n", + " return Panel(\n", + " bs.Heading(value, level=2, class_name=\"mb-1\", style=style(color=accent)),\n", + " bs.Text(label, class_name=\"text-secondary\"),\n", + " )\n", + "\n", + "def OrderSummary(data):\n", + " rows = [dict(row, amount=Decimal(row[\"unit_price\"]) * row[\"quantity\"])\n", + " for row in data[\"items\"]]\n", + " total = sum((row[\"amount\"] for row in rows), Decimal(\"0.00\"))\n", + " units = sum(row[\"quantity\"] for row in rows)\n", + " details = bs.DataTable(\n", + " rows,\n", + " columns=[\n", + " bs.Column(\"Item\", lambda row: Strong(row[\"item\"])),\n", + " bs.Column(\"Quantity\", lambda row: row[\"quantity\"], align=\"right\"),\n", + " bs.Column(\"Amount\", lambda row: money(row[\"amount\"]), align=\"right\"),\n", + " ],\n", + " striped=True,\n", + " caption=\"Order contents · USD\",\n", + " ) if rows else bs.Alert(\"No items in this order.\", variant=\"light\")\n", + " return Document(\n", + " bs.Box(bs.Text(\"NORTHSTAR / STUDIO\", class_name=\"fw-bold\"),\n", + " bs.Badge(data[\"status\"], variant=\"light\", class_name=\"ms-3\"),\n", + " class_name=\"mb-4\"),\n", + " bs.Heading(data[\"collection\"], level=1,\n", + " style=style(font_size=pt(title_size), color=accent, line_height=1.1)),\n", + " bs.Paragraph(f'Prepared for {data[\"customer\"]}', class_name=\"lead mb-4\"),\n", + " bs.Row(\n", + " bs.Col(Metric(str(units), \"Items in this order\"), span=5),\n", + " bs.Col(Metric(money(total), \"Order total\"), span=7),\n", + " gutter=3, class_name=\"mb-4 mx-0\",\n", + " ),\n", + " Panel(details, title=f'Order {data[\"number\"]}',\n", + " footer=\"Made with care. Packed with purpose.\").with_parts(\n", + " title=style(color=accent, font_size=pt(16)),\n", + " body=style(padding=\"1.25rem\"),\n", + " ),\n", + " bs.Alert(\"Please retain this copy with your shipment.\",\n", + " class_name=\"dispatch-note mt-4\"),\n", + " title=f'Order {data[\"number\"]}',\n", + " page=Page(size=\"A4\", margin=mm(16)),\n", + " theme=Theme(text=style(font_family=\"Inter, Arial, sans-serif\", font_size=pt(10),\n", + " color=\"#18382f\", line_height=1.4)),\n", + " styles={\".dispatch-note\": style(color=accent, border_color=accent,\n", + " background_color=\"#f0f5f2\")},\n", + " )\n", + "\n", + "document = OrderSummary(order)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "render-help", + "metadata": {}, + "source": [ + "## 5. Render and export\n", + "\n", + "The PDF renderer receives the bundled Inter font file explicitly. A CSS\n", + "family name does not load a font. Styled HTML keeps the family declaration;\n", + "its browser host supplies Inter separately, or uses the Arial/sans-serif fallback.\n", + "\n", + "`include_styles=False` removes generated and inline styling but keeps classes\n", + "for a host application's CSS. Both export modes also work on an individual\n", + "component, such as `Metric(\"44\", \"Items\").to_html(include_styles=False)`.\n", + "Styled Bootstrap fragments include global Bootstrap rules; use unstyled\n", + "fragments when the host page already provides its stylesheet." + ] + }, + { + "cell_type": "code", + "id": "render-export", + "metadata": {}, + "source": [ + "from importlib.resources import files\n", + "from pathlib import Path\n", + "from fullbleed.ui.experimental import Renderer\n", + "\n", + "output = Path(\"output\")\n", + "output.mkdir(exist_ok=True)\n", + "font = files(\"fullbleed_assets\").joinpath(\"fonts/Inter-Variable.ttf\")\n", + "renderer = Renderer(font_files=[str(font)],\n", + " document_timestamp=\"2026-10-01T00:00:00Z\")\n", + "prepared = document.prepare(renderer)\n", + "pdf_path = output / \"order.pdf\"\n", + "pdf_path.write_bytes(prepared.render_pdf())\n", + "(output / \"order.styled.html\").write_text(\n", + " document.to_html(include_styles=True), encoding=\"utf-8\", newline=\"\\n\")\n", + "(output / \"order.unstyled.html\").write_text(\n", + " document.to_html(include_styles=False), encoding=\"utf-8\", newline=\"\\n\")\n", + "print(f\"Saved {pdf_path} and both HTML exports.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "preview", + "metadata": {}, + "source": [ + "from IPython.display import Image, display\n", + "\n", + "preview_paths = []\n", + "for number, png in enumerate(prepared.render_image_pages(dpi=110), start=1):\n", + " path = output / f\"order-page-{number}.png\"\n", + " path.write_bytes(png)\n", + " preview_paths.append(path)\n", + " display(Image(data=png, width=650))\n", + "print(f\"{len(preview_paths)} page(s). Review every page before using the document.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "download-help", + "metadata": {}, + "source": [ + "## 6. Download your files\n", + "\n", + "This ZIP contains the current PDF, both HTML exports, and every current page\n", + "preview. In Colab the next cell starts the download. In local Jupyter it\n", + "displays a link. The runtime's `output/` folder also contains the individual\n", + "files. If you edit the order or styles, rerun from that edit through this cell." + ] + }, + { + "cell_type": "code", + "id": "download", + "metadata": {}, + "source": [ + "import sys\n", + "from zipfile import ZipFile, ZIP_DEFLATED\n", + "\n", + "bundle_path = output / \"component-order.zip\"\n", + "current_files = [pdf_path, output / \"order.styled.html\",\n", + " output / \"order.unstyled.html\", *preview_paths]\n", + "with ZipFile(bundle_path, \"w\", compression=ZIP_DEFLATED) as bundle:\n", + " for path in current_files:\n", + " bundle.write(path, arcname=path.name)\n", + "\n", + "if \"google.colab\" in sys.modules:\n", + " from google.colab import files\n", + " files.download(str(bundle_path))\n", + "else:\n", + " from IPython.display import FileLink\n", + " display(FileLink(bundle_path.as_posix()))" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "next-steps", + "metadata": {}, + "source": [ + "## Keep composing\n", + "\n", + "- [Component guide and catalog](https://docs.fullbleed.dev/guides/bootstrap-components-preview/)\n", + " explain class names, named parts, fonts, static states and current limits.\n", + "- [Northstar and Common Ground source kits](https://docs.fullbleed.dev/guides/showcase-authoring/)\n", + " show a complete invoice and illustrated report in components and HTML/CSS.\n", + "- [The authored accessible guide](https://docs.fullbleed.dev/guides/accessible-showcase/)\n", + " includes the exact PDF, semantic source and scoped validation receipts.\n", + "- [Share an authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86)\n", + " if a composition or styling task feels awkward. Include the small component,\n", + " expected result and Fullbleed version; use fictional data in public posts.\n", + "- [Stable Python guide](https://docs.fullbleed.dev/guides/python-components/)\n", + " documents the established API separately from this alpha.\n", + "\n", + "The notebook and Fullbleed are MIT licensed. This example does not establish\n", + "complete Bootstrap CSS, browser, accessibility or PDF conformance." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "colab": { + "name": "bootstrap_components.ipynb", + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/assets/component-notebook-alpha6-r1/browser-verification.json b/docs/assets/component-notebook-alpha6-r1/browser-verification.json new file mode 100644 index 0000000..e6c1325 --- /dev/null +++ b/docs/assets/component-notebook-alpha6-r1/browser-verification.json @@ -0,0 +1,86 @@ +{ + "ok": true, + "checked_at": "2026-10-18T16:41:19.430606+00:00", + "notebook_sha256": "38007bb834b8affd0e2062cdba77eee56c2eae25c51ded9f37c6d40066b8e997", + "runtime_version": "2.6.0a6", + "system": "Windows", + "python": "3.11.8", + "browser": "155.0.8059.40", + "tools": { + "notebook": "7.6.3", + "jupyter_server": "2.21.1", + "ipykernel": "7.4.0", + "playwright": "1.49.1" + }, + "cases": [ + { + "name": "default", + "pages": 1, + "actual_browser_download": true, + "filename": "component-order.zip", + "href_path": "/files/output/component-order.zip", + "zip_sha256": "559869425c983a7f71f7e12ed10c4b827982e30f0d0959c1fa7418cb3d60bcb3", + "artifact_hashes": { + "order.pdf": "e273572ff4885ba8b139563cec45b590b385a2e7330d8728d2f71596e2fdbce6", + "order.styled.html": "c53d95a831b1d2fda92da28e6dcc5bf6063a7b1090cf1053930c39ff7b54103f", + "order.unstyled.html": "3e072e3dd684f9e7c483859a41051619effa814225a64a1a0e500e4d6be936e6", + "order-page-1.png": "2b6c470b1e49a906170be982a00398ed57033b353cb14e6b56f97763b99bbd2b" + }, + "matches_independent_reference": true, + "visible_previews_match_download": true + }, + { + "name": "edited", + "pages": 1, + "actual_browser_download": true, + "filename": "component-order.zip", + "href_path": "/files/output/component-order.zip", + "zip_sha256": "2fda77249124c3c4492661fa3ecf8951fb0c4c609194d8cdac76ac9ed6554a11", + "artifact_hashes": { + "order.pdf": "5823731e0146ab74806acce6bbd0676af5b77bf04566827c9c50523004cdb4c4", + "order.styled.html": "ffeb7c3da32ee2f88ffca18fdf98a57207217d35dfa9a8b6ad86b889ef763f49", + "order.unstyled.html": "7079de5ca04cb177864165f59f3a77ddf5d3d4406fb274973ea6bbf0b6254fac", + "order-page-1.png": "a9610eb13d8b47f2f643a0809de412302428d772e9798e1ab17d3f9361c69fa7" + }, + "matches_independent_reference": true, + "visible_previews_match_download": true + }, + { + "name": "long", + "pages": 4, + "actual_browser_download": true, + "filename": "component-order.zip", + "href_path": "/files/output/component-order.zip", + "zip_sha256": "72eb4fccf02ae5b5e9bbc3d37a1b3e4b8cfff61d89b026eebb0251b4757ffc42", + "artifact_hashes": { + "order.pdf": "c543c6cd5c6dadc7b4e77fd043a3fa5f2693dfbb0e20d9f844a5e6bb894a4735", + "order.styled.html": "40b7281978170db2c4da4ca73ab86c54e367014f66d1ae64ea279b83a44f148d", + "order.unstyled.html": "a9a83ab877726691b998e0b53fef913a927044c9f1dbaa422fc2a47d18fecbf8", + "order-page-1.png": "d9dac9397542e717cf089da666509b3511e0700134dd6b77fe77bbe6751450f8", + "order-page-2.png": "cba70b5c9faf533ff81d3ae6d0cfee2a08b5edca98de02a38f05ed0c471f2eea", + "order-page-3.png": "20953665812775ad39f9c1b02b80f1d083a5ac24d4e98150e091bf59cee01881", + "order-page-4.png": "9c7b49a79e37676a917621f4e603dcd1f1bd961ee1c2743b361a910ce4632e33" + }, + "matches_independent_reference": true, + "visible_previews_match_download": true + }, + { + "name": "empty-after-long", + "pages": 1, + "actual_browser_download": true, + "filename": "component-order.zip", + "href_path": "/files/output/component-order.zip", + "zip_sha256": "f011e7c092d956e9f978fa71d9fe16806d115d259f7bda05be2cc7bcf2f842ec", + "artifact_hashes": { + "order.pdf": "5994d1a6f3a7176c8f4f614221cd60d27028a1fd8d80a372f4510eeeb056b573", + "order.styled.html": "7f7576f19793dbb00055d236a6e42a31970d0eee7a6bf20a966aaec80542b934", + "order.unstyled.html": "8d287583c01c902d4d58f400e36c7d3437631999aacfc16f146955a1311b130a", + "order-page-1.png": "d18340f788498d3f1912c80675562eaa47353e85a6e0a5bb6837dbf0a6ced454" + }, + "matches_independent_reference": true, + "visible_previews_match_download": true + } + ], + "stale_previews_excluded": true, + "scope": "Local Jupyter Notebook editing, execution, displayed previews and actual browser ZIP downloads. Hosted Colab is not tested." +} diff --git a/docs/assets/component-notebook-alpha6-r1/order-page-1.png b/docs/assets/component-notebook-alpha6-r1/order-page-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0303b90867ad0b8a9898060cda72bad987ab5e8d GIT binary patch literal 78032 zcmeFZ_cxqx^foGqd?Z0gB6^G7OO(+?@4fekUPm8&5($DJM2Q{*Q76i1V@L?1moYk{ z_ud)Cd9uFeoFCr*;9c))&03E&c;O9TTtdH{Gqh`L5Hw^X~*^#)WnFUtWdN<2cff5iH==^XJL7l>a^e z@7J~Nc`(7h@9;9CfBahop5r6E=l`z#WCdLRu3tG7js9H)3_h0qyS^?aH3HUz_e=9d z*}r?d&5$>||L+Gvk^lDw|3~b>YzXom-ucrG0b#pgG~Yd?WU@#bz%*X({pGu0CH__5 zVqT7dOx4OIvn2MtP&=~Te}ISgDvs0$(fp|;d$#?qgI`}hw7_MjVlAr+3&LJ@^I zqN;jy_&B(@gI23c*0gdwLpN(}!Y6B>L!V`C!f^*sd^uJ7QJaa1=@zIC(vXSlfyx=f z#nrQ+z^0~s37-RK+s&BYDv`o*(6yag11Y|quD;ajriu|4x|<&<E%n$ z2Ol;4pkKt7JIN7D>T_8e9NF}bu{dkNt(HXV34#)xP-bKO+{a18v?NB9V#-Qo$z+>v2nR5JUkop{7?)rKn zuUPNY$5ma=taS65GlPn|-d(IUpu8|Atae?!9K~j(LH-E8kbqb)XHcL&0%Ri|c9?Bm z_~a2jb^O_%ra!eNd^cx3>B~_>L;QTMs|R27tBl7CZM1YmtZajflw6d8)S4Ev$HpdV zp2hVMJAGyR=tkw@7T{qe5H=n?)Fz*7U-`YYqDH
~>lkM#%I+J@^TsTZyt2Xeu$ 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

nIV3Ba$L@ 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%0A)o50{X+#Y#%y1*{31msZZ;yf8vx~tA@4@_ll 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 literal 0 HcmV?d00001 diff --git a/docs/assets/component-notebook-alpha6-r1/order.styled.html b/docs/assets/component-notebook-alpha6-r1/order.styled.html new file mode 100644 index 0000000..42fbeb4 --- /dev/null +++ b/docs/assets/component-notebook-alpha6-r1/order.styled.html @@ -0,0 +1,39 @@ +Order 1048
NORTHSTAR / STUDIOReady to ship

The autumn collection

Prepared for Moss & Fern Studio

44

Items in this order

USD 1,056.00

Order total

Order 1048

Order contents · USD
ItemQuantityAmount
Studio notebooks24USD 360.00
Cotton stationery sets12USD 216.00
Archival prints8USD 480.00
Please retain this copy with your shipment.
\ No newline at end of file diff --git a/docs/assets/component-notebook-alpha6-r1/order.unstyled.html b/docs/assets/component-notebook-alpha6-r1/order.unstyled.html new file mode 100644 index 0000000..fe4de62 --- /dev/null +++ b/docs/assets/component-notebook-alpha6-r1/order.unstyled.html @@ -0,0 +1 @@ +Order 1048
NORTHSTAR / STUDIOReady to ship

The autumn collection

Prepared for Moss & Fern Studio

44

Items in this order

USD 1,056.00

Order total

Order 1048

Order contents · USD
ItemQuantityAmount
Studio notebooks24USD 360.00
Cotton stationery sets12USD 216.00
Archival prints8USD 480.00
Please retain this copy with your shipment.
\ No newline at end of file diff --git a/docs/assets/component-notebook-alpha6-r1/verification.json b/docs/assets/component-notebook-alpha6-r1/verification.json new file mode 100644 index 0000000..7c02ceb --- /dev/null +++ b/docs/assets/component-notebook-alpha6-r1/verification.json @@ -0,0 +1,67 @@ +{ + "runtime_version": "2.6.0a6", + "notebook_sha256": "38007bb834b8affd0e2062cdba77eee56c2eae25c51ded9f37c6d40066b8e997", + "cases": [ + { + "name": "default", + "pages": 1, + "row_count": 3, + "total": "1056.00", + "pdf_sha256": "e273572ff4885ba8b139563cec45b590b385a2e7330d8728d2f71596e2fdbce6", + "accent": "#205b47", + "rich_cell_components_checked": true + }, + { + "name": "edited", + "pages": 1, + "row_count": 3, + "total": "1176.00", + "pdf_sha256": "5823731e0146ab74806acce6bbd0676af5b77bf04566827c9c50523004cdb4c4", + "accent": "#763c96", + "rich_cell_components_checked": true + }, + { + "name": "long", + "pages": 4, + "row_count": 72, + "total": "684.00", + "pdf_sha256": "c543c6cd5c6dadc7b4e77fd043a3fa5f2693dfbb0e20d9f844a5e6bb894a4735", + "accent": "#205b47", + "rich_cell_components_checked": true + }, + { + "name": "empty-after-long", + "pages": 1, + "row_count": 0, + "total": "0.00", + "pdf_sha256": "5994d1a6f3a7176c8f4f614221cd60d27028a1fd8d80a372f4510eeeb056b573", + "accent": "#205b47", + "rich_cell_components_checked": true + } + ], + "downloads": { + "bootstrap_components.ipynb": "38007bb834b8affd0e2062cdba77eee56c2eae25c51ded9f37c6d40066b8e997", + "order.pdf": "e273572ff4885ba8b139563cec45b590b385a2e7330d8728d2f71596e2fdbce6", + "order-page-1.png": "d84d30060d7593e28dbc9ae16cb0f7c4ef1a013f83ed82cff9df8e9519eae1fa", + "order.styled.html": "c53d95a831b1d2fda92da28e6dcc5bf6063a7b1090cf1053930c39ff7b54103f", + "order.unstyled.html": "3e072e3dd684f9e7c483859a41051619effa814225a64a1a0e500e4d6be936e6" + }, + "preview": "PDFium rendering of the default output PDF at 110 DPI", + "checks": [ + "Notebook installation and execution", + "Independent PDF text and page bounds", + "Requested PDF accent colours", + "Repeat/direct PDF byte equality", + "Static styled and unstyled HTML", + "Rich item cell emphasis, order and escaped text in both HTML exports", + "Exact ZIP contents", + "Long-to-empty rerun excludes old pages" + ], + "scope": "Executed Jupyter cells and independent PDF/HTML/ZIP checks. Hosted Colab runtime and download UI, exhaustive CSS/browser parity and standards conformance are not evaluated.", + "browser_verification": { + "file": "browser-verification.json", + "sha256": "78aeabb0c3a3cfa720f3b7718cda620cc9f31a20654ed6c6b370fccfa982ae05", + "system": "Windows", + "scope": "Local Jupyter Notebook editing, execution, displayed previews and actual browser ZIP downloads. Hosted Colab is not tested." + } +} diff --git a/docs/getting-started/component-notebook.md b/docs/getting-started/component-notebook.md index 3324387..3265362 100644 --- a/docs/getting-started/component-notebook.md +++ b/docs/getting-started/component-notebook.md @@ -11,9 +11,9 @@ The notebook contains the complete source, with no authored HTML strings. Item cells return `Strong(...)` components, so emphasis stays part of the component tree. -[Open in Colab](https://colab.research.google.com/github/fullbleed-engine/docs/blob/ee8cfbef90116c4e1ea2db7c0f005dce946a11a8/examples/component-notebook-alpha6/bootstrap_components.ipynb){ .md-button .md-button--primary } -[Download the notebook](../assets/component-notebook-alpha6/bootstrap_components.ipynb){ .md-button download="bootstrap_components.ipynb" } -[View the source](https://github.com/fullbleed-engine/docs/blob/ee8cfbef90116c4e1ea2db7c0f005dce946a11a8/examples/component-notebook-alpha6/bootstrap_components.ipynb){ .md-button } +[Open in Colab](https://colab.research.google.com/github/fullbleed-engine/docs/blob/43aff6dee9692d371fa49221c4bcb99045d12639/examples/component-notebook-alpha6-r1/bootstrap_components.ipynb){ .md-button .md-button--primary } +[Download the notebook](../assets/component-notebook-alpha6-r1/bootstrap_components.ipynb){ .md-button download="bootstrap_components.ipynb" } +[View the source](https://github.com/fullbleed-engine/docs/blob/43aff6dee9692d371fa49221c4bcb99045d12639/examples/component-notebook-alpha6-r1/bootstrap_components.ipynb){ .md-button } !!! warning "Opt-in component alpha" The notebook installs **Fullbleed 2.6.0a6**. Its APIs under @@ -22,7 +22,7 @@ component tree. is included. The [stable Python guide](../guides/python-components.md) documents the established API. -## Run it, then make an edit +## Run in Colab 1. Open the notebook, sign in to Google if prompted, and choose **Runtime > Run all**. 2. Edit the order data in step 2 or the accent and title size in step 3. @@ -32,20 +32,60 @@ component tree. Use a standard CPU runtime; this example needs no GPU or paid plan. Colab runs the code on a hosted machine, applies its [own availability and usage limits](https://research.google.com/colaboratory/faq.html), and deletes runtime files after the session expires. Download your results before -leaving. You can also open the notebook in local Jupyter; the same cells display -a download link there. +leaving. + +## Run in local Jupyter + +Download the notebook above. In the folder containing `bootstrap_components.ipynb`, +create a virtual environment and launch Jupyter with these commands. + +**Windows (PowerShell):** + +```powershell +py -m venv .venv +.\.venv\Scripts\python.exe -m pip install "notebook==7.6.3" +.\.venv\Scripts\python.exe -m notebook bootstrap_components.ipynb +``` + +**macOS or Linux:** + +```bash +python3 -m venv .venv +.venv/bin/python -m pip install "notebook==7.6.3" +.venv/bin/python -m notebook bootstrap_components.ipynb +``` + +In the browser, choose **Run > Run All Cells**. The first cell installs the pinned +Fullbleed alpha in this environment. When the kernel finishes, review the page +previews and click **output/component-order.zip** in step 6. + +After editing the data or styles, use **Run > Run All Cells** again and download +the new ZIP. Its individual PDF, HTML and PNG files also remain in the notebook's +local `output/` folder. This notebook revision normalizes the download URL on +Windows so the browser saves the ZIP as `component-order.zip`. ## What you will make -[![A Northstar order summary with green headings, quantity and total cards, a three-row table and a shipment note.](../assets/component-notebook-alpha6/order-page-1.png){ width="480" }](../assets/component-notebook-alpha6/order.pdf) +[![A Northstar order summary with green headings, quantity and total cards, a three-row table and a shipment note.](../assets/component-notebook-alpha6-r1/order-page-1.png){ width="480" }](../assets/component-notebook-alpha6-r1/order.pdf) -[Open the sample PDF](../assets/component-notebook-alpha6/order.pdf){ .md-button } -[Open styled HTML](../assets/component-notebook-alpha6/order.styled.html){ .md-button } -[Open unstyled HTML](../assets/component-notebook-alpha6/order.unstyled.html){ .md-button } +[Open the sample PDF](../assets/component-notebook-alpha6-r1/order.pdf){ .md-button } +[Open styled HTML](../assets/component-notebook-alpha6-r1/order.styled.html){ .md-button } +[Open unstyled HTML](../assets/component-notebook-alpha6-r1/order.unstyled.html){ .md-button } The fictional order contains 44 items totaling USD 1,056.00. Python `Decimal` calculates the total; the example omits tax, shipping and discounts. Try an empty -item list or add rows to explore pagination. +item list or add rows to explore pagination. For the 72-row example, append +this to the order-data cell in step 2 and rerun from there: + +```python +order["items"] = [ + {"item": f"Edition {n:02d} / art print", "quantity": 1, "unit_price": "9.50"} + for n in range(1, 73) +] +``` + +Replace that assignment with `order["items"] = []` to try the empty state. +The download includes only the current order's pages, even after a longer run. ## See how styling connects @@ -74,9 +114,15 @@ compare repeat renders; and check both HTML exports and the ZIP's exact contents that rich item cells preserve their emphasis, order and escaped text. Old page previews are excluded from a shorter order's download. -The [verification record](../assets/component-notebook-alpha6/verification.json) includes -the sample and notebook hashes. These checks do not execute a hosted Colab runtime -or its download UI, or establish complete CSS, browser or PDF-standard conformance. +The [verification record](../assets/component-notebook-alpha6-r1/verification.json) includes +the sample and notebook hashes. A separate +[Jupyter browser record](../assets/component-notebook-alpha6-r1/browser-verification.json) +covers all four cases in Windows Chrome with Notebook 7.6.3: editing cells, +running them, displaying every preview, and clicking the actual ZIP download. +Every downloaded PDF, HTML file and PNG matches the independently checked output. + +These checks do not execute a hosted Colab runtime or its download UI, or establish +complete CSS, browser or PDF-standard conformance. [Share a small authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86) if a composition or styling task feels awkward. Use fictional data in public examples. @@ -84,3 +130,5 @@ if a composition or styling task feels awkward. Use fictional data in public exa The [archived alpha 3 notebook](https://colab.research.google.com/github/fullbleed-engine/docs/blob/8cf4915321f2faf517ad7e5b0725e83754e227a6/examples/component-notebook/bootstrap_components.ipynb), [sample PDF](../assets/component-notebook/order.pdf) and [archived verification record](../assets/component-notebook/verification.json) retain their original runtime and bytes. The [alpha 5 notebook guide](https://github.com/fullbleed-engine/docs/blob/9aa0b2d9b26d398df58f55c8dd3b240a2457e96e/docs/getting-started/component-notebook.md) retains its original pinned notebook and sample files. + +The [original alpha 6 notebook guide](https://github.com/fullbleed-engine/docs/blob/ba2540e2603787593189c1ae836b1c61702e313d/docs/getting-started/component-notebook.md) retains its original notebook and sample files. This revision uses the same Fullbleed 2.6.0a6 runtime and produces identical documents. diff --git a/tools/build_component_notebook_samples.py b/tools/build_component_notebook_samples.py index 71648a1..c4e39d7 100644 --- a/tools/build_component_notebook_samples.py +++ b/tools/build_component_notebook_samples.py @@ -9,12 +9,13 @@ ROOT = Path(__file__).resolve().parents[1] parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--verification-dir", required=True, type=Path) -parser.add_argument("--channel", choices=("alpha3", "alpha5", "alpha6"), default="alpha3") +parser.add_argument("--browser-verification-dir", type=Path, help="Optional actual Jupyter browser check for this exact notebook") +parser.add_argument("--channel", choices=("alpha3", "alpha5", "alpha6", "alpha6-r1"), default="alpha3") args = parser.parse_args() checked = args.verification_dir.resolve() report = json.loads((checked / "verification.json").read_text(encoding="utf-8")) folder = "component-notebook" if args.channel == "alpha3" else f"component-notebook-{args.channel}" -runtime_version = {"alpha3": "2.6.0a3", "alpha5": "2.6.0a5", "alpha6": "2.6.0a6"}[args.channel] +runtime_version = {"alpha3": "2.6.0a3", "alpha5": "2.6.0a5", "alpha6": "2.6.0a6", "alpha6-r1": "2.6.0a6"}[args.channel] source = ROOT / "examples" / folder / "bootstrap_components.ipynb" assert report["ok"] and report["runtime_version"] == runtime_version assert report["notebook_sha256"] == sha256(source.read_bytes()).hexdigest() @@ -45,5 +46,20 @@ for row in manifest["cases"]: row["rich_cell_components_checked"] = True manifest["checks"].insert(5, "Rich item cell emphasis, order and escaped text in both HTML exports") +if args.browser_verification_dir: + browser_path = args.browser_verification_dir / "verification.json" + browser = json.loads(browser_path.read_text(encoding="utf-8")) + assert browser["ok"] and browser["notebook_sha256"] == report["notebook_sha256"] + assert browser["runtime_version"] == report["runtime_version"] + assert [r["name"] for r in browser["cases"]] == [r["name"] for r in report["cases"]] + for actual, expected in zip(browser["cases"], report["cases"]): + assert actual["actual_browser_download"] and actual["visible_previews_match_download"] + assert actual["filename"] == "component-order.zip" + assert actual["artifact_hashes"] == expected["artifact_hashes"] + assert browser["stale_previews_excluded"] + shutil.copyfile(browser_path, assets / "browser-verification.json") + manifest["browser_verification"] = {"file": "browser-verification.json", + "sha256": sha256(browser_path.read_bytes()).hexdigest(), "system": browser["system"], + "scope": browser["scope"]} (assets / "verification.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8", newline="\n") print(json.dumps({"ok": True, "downloads": len(manifest["downloads"]), "cases": len(manifest["cases"])})) diff --git a/tools/check_component_notebook_jupyter.py b/tools/check_component_notebook_jupyter.py new file mode 100644 index 0000000..e6859f2 --- /dev/null +++ b/tools/check_component_notebook_jupyter.py @@ -0,0 +1,219 @@ +"""Edit and run the notebook in a browser, then verify actual ZIP downloads. + +Run verify_component_notebook.py first and supply its --out as --reference. +The reference supplies independently inspected PDF/HTML/PNG bytes. A private +loopback Jupyter server and a separate headless browser are stopped on exit. +""" +from __future__ import annotations + +import argparse +import ast +import base64 +from copy import deepcopy +from datetime import datetime, timezone +from hashlib import sha256 +import importlib.metadata +import json +import os +from pathlib import Path +import platform +import secrets +import shutil +import socket +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request +from urllib.parse import urlsplit +from zipfile import ZipFile + +from playwright.sync_api import expect, sync_playwright + +ROOT = Path(__file__).resolve().parents[1] + + +def write(path, value): + path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8", newline="\n") + + +def check_browser(page, context, source, work, out, reference, token): + source_cells = {c["id"]: "".join(c["source"]) for c in source["cells"] if c["cell_type"] == "code"} + assert len(source_cells) == 7 + order = ast.literal_eval(ast.parse(source_cells["order-data"]).body[0].value) + downloads = [] + context.on("page", lambda child: child.on("download", lambda item: downloads.append(item))) + page.on("download", lambda item: downloads.append(item)) + reports = [] + page.get_by_role("menuitem", name="Run", exact=True).wait_for(timeout=60000) + expect(page.get_by_role("textbox")).to_have_count(7) + for index, name in enumerate(("default", "edited", "long", "empty-after-long")): + destination = out / name + destination.mkdir() + if index: + data = deepcopy(order) + accent, size = "#205b47", 30 + if name == "edited": + data.update(number="1051", customer="Moss & Fern ", status="Packed") + data["items"][0].update(quantity=32, item="Studio & cards") + data["items"][1]["item"] = "Personalized note cards" + accent, size = "#763c96", 26 + elif name in {"long", "empty-after-long"}: + data["items"] = [] + data_source = "order = " + repr(data) + if name == "long": + data_source += "\norder['items'] = [\n dict(item=f'Edition {n:02d} / art print', quantity=1, unit_price='9.50')\n for n in range(1, 73)\n]" + for position, text in ((1, data_source), (2, f"accent = {accent!r}\ntitle_size = {size!r}")): + editor = page.get_by_role("textbox").nth(position) + editor.scroll_into_view_if_needed() + editor.fill(text) + assert "\n".join(editor.locator("div.cm-line").all_text_contents()) == text + page.get_by_role("menuitem", name="Run", exact=True).click() + page.get_by_role("menuitem", name="Run All Cells", exact=True).click() + page.get_by_text(f"[{(index + 1) * 7}]:", exact=True).wait_for(timeout=180000) + expect(page.get_by_role("progressbar", name="Kernel status")).to_have_attribute("aria-valuenow", "100", timeout=30000) + link = page.get_by_role("link", name="output/component-order.zip", exact=True) + link.wait_for() + href_path = urlsplit(link.get_attribute("href")).path + assert href_path == "/files/output/component-order.zip", href_path + page.get_by_role("button", name="Save and create checkpoint (Ctrl+S)", exact=True).click() + downloads.clear() + link.click() + deadline = time.monotonic() + 15 + while not downloads and time.monotonic() < deadline: + page.wait_for_timeout(100) + assert len(downloads) == 1, "Expected a real browser download, including a temporary download tab" + download = downloads[0] + assert download.suggested_filename == "component-order.zip", download.suggested_filename + assert download.failure() is None + archive = destination / download.suggested_filename + download.save_as(archive) + expected = next(row for row in reference["cases"] if row["name"] == name) + hashes = {} + with ZipFile(archive) as bundle: + assert bundle.testzip() is None + assert len(bundle.namelist()) == len(set(bundle.namelist())) + assert set(bundle.namelist()) == set(expected["zip_files"]) + for filename in bundle.namelist(): + content = bundle.read(filename) + assert sha256(content).hexdigest() == expected["artifact_hashes"][filename], (name, filename) + assert content == (work / "output" / filename).read_bytes(), (name, filename) + hashes[filename] = sha256(content).hexdigest() + previews = page.get_by_role("feed", name="Cells").get_by_role("img") + assert previews.count() == expected["pages"] + for number in range(expected["pages"]): + preview = previews.nth(number) + preview.scroll_into_view_if_needed() + image = preview.evaluate("img => ({complete: img.complete, width: img.naturalWidth, src: img.src})") + assert image["complete"] and image["width"] > 0 + assert image["src"].startswith("data:image/png;base64,") + assert sha256(base64.b64decode(image["src"].split(",", 1)[1])).hexdigest() == hashes[f"order-page-{number + 1}.png"] + preview.screenshot(path=str(destination / f"visible-preview-{number + 1}.png")) + link.scroll_into_view_if_needed() + page.screenshot(path=str(destination / "notebook-download.png")) + snapshot = page.locator("body").aria_snapshot().replace(token, "[redacted]") + (destination / "notebook.txt").write_text(snapshot, encoding="utf-8") + report = dict(name=name, pages=expected["pages"], actual_browser_download=True, + filename=download.suggested_filename, href_path=href_path, + zip_sha256=sha256(archive.read_bytes()).hexdigest(), artifact_hashes=hashes, + matches_independent_reference=True, visible_previews_match_download=True) + write(destination / "verification.json", report) + reports.append(report) + print(json.dumps(dict(case=name, pages=expected["pages"], browser_download=True)), flush=True) + assert (work / "output/order-page-4.png").exists() + assert "order-page-2.png" not in reports[-1]["artifact_hashes"] + return reports + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--reference", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + parser.add_argument("--browser", choices=("chrome", "chromium"), default="chrome") + args = parser.parse_args() + out = args.out.resolve() + assert not out.exists(), "Use a new output directory to retain earlier evidence" + reference = json.loads((args.reference / "verification.json").read_text(encoding="utf-8")) + source_path = ROOT / reference["notebook"] + assert reference["ok"] and reference["install_cell_executed"] + assert reference["notebook"] == "examples/component-notebook-alpha6-r1/bootstrap_components.ipynb" + assert sha256(source_path.read_bytes()).hexdigest() == reference["notebook_sha256"] + source = json.loads(source_path.read_text(encoding="utf-8")) + assert all(not c["outputs"] and c["execution_count"] is None for c in source["cells"] if c["cell_type"] == "code") + out.mkdir(parents=True) + work = out / "work" + work.mkdir() + shutil.copyfile(source_path, work / source_path.name) + environment = {k: v for k, v in os.environ.items() if not k.startswith(("JUPYTER_", "IPYTHON"))} + token = secrets.token_urlsafe(32) + environment.update(JUPYTER_TOKEN=token, PYTHONUTF8="1") + with tempfile.TemporaryDirectory(prefix="fullbleed-notebook-browser-") as private: + private = Path(private) + for key, name in (("JUPYTER_CONFIG_DIR", "config"), ("JUPYTER_DATA_DIR", "data"), + ("JUPYTER_RUNTIME_DIR", "runtime"), ("IPYTHONDIR", "ipython")): + (private / name).mkdir() + environment[key] = str(private / name) + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + base = f"http://127.0.0.1:{port}" + with (private / "server.log").open("w", encoding="utf-8") as log: + server = subprocess.Popen([sys.executable, "-m", "notebook", "--no-browser", "--ServerApp.ip=127.0.0.1", + f"--ServerApp.port={port}", "--ServerApp.port_retries=0", f"--ServerApp.root_dir={work}", + "--ServerApp.shutdown_no_activity_timeout=600"], cwd=work, env=environment, + stdout=log, stderr=subprocess.STDOUT, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0) + try: + deadline = time.monotonic() + 60 + while True: + assert server.poll() is None, "The private Jupyter server exited" + try: + request = urllib.request.Request(base + "/api/status", headers={"Authorization": "token " + token}) + with urllib.request.urlopen(request, timeout=2) as response: + assert response.status == 200 + break + except (urllib.error.URLError, TimeoutError): + if time.monotonic() >= deadline: + raise RuntimeError("Private Jupyter server did not become ready") from None + time.sleep(0.2) + with sync_playwright() as p: + options = {"channel": "chrome"} if args.browser == "chrome" else {} + browser = p.chromium.launch(**options) + try: + context = browser.new_context(viewport={"width": 1440, "height": 1000}, accept_downloads=True) + page = context.new_page() + page.goto(base + "/tree?token=" + token, wait_until="domcontentloaded") + page.goto(base + "/notebooks/" + source_path.name, wait_until="domcontentloaded") + cases = check_browser(page, context, source, work, out, reference, token) + browser_version = browser.version + finally: + browser.close() + finally: + if server.poll() is None: + try: + request = urllib.request.Request(base + "/api/shutdown", data=b"", method="POST", + headers={"Authorization": "token " + token}) + with urllib.request.urlopen(request, timeout=10): + pass + server.wait(timeout=30) + except (urllib.error.URLError, TimeoutError, subprocess.TimeoutExpired): + server.terminate() + server.wait(timeout=15) + write(out / "server-stopped.json", dict(stopped=server.poll() is not None, returncode=server.returncode)) + log.flush() + sanitized = (private / "server.log").read_text(encoding="utf-8", errors="replace").replace(token, "[redacted]") + (out / "server.log").write_text(sanitized, encoding="utf-8") + report = dict(ok=True, checked_at=datetime.now(timezone.utc).isoformat(), + notebook_sha256=reference["notebook_sha256"], runtime_version=importlib.metadata.version("fullbleed"), + system=platform.system(), python=platform.python_version(), browser=browser_version, + tools={name: importlib.metadata.version(name) for name in ("notebook", "jupyter_server", "ipykernel", "playwright")}, + cases=cases, stale_previews_excluded=True, + scope="Local Jupyter Notebook editing, execution, displayed previews and actual browser ZIP downloads. Hosted Colab is not tested.") + assert report["runtime_version"] == reference["runtime_version"] + write(out / "verification.json", report) + print(json.dumps(dict(ok=True, cases=len(cases), pages=sum(c["pages"] for c in cases)))) + + +if __name__ == "__main__": + main() diff --git a/tools/check_component_notebook_ui.py b/tools/check_component_notebook_ui.py index c9abba2..5ae19a7 100644 --- a/tools/check_component_notebook_ui.py +++ b/tools/check_component_notebook_ui.py @@ -20,7 +20,7 @@ def main(): args = parser.parse_args() root = args.site.resolve() args.out.mkdir(parents=True, exist_ok=True) - folder = "component-notebook-alpha6" + folder = "component-notebook-alpha6-r1" assets = root / "assets" / folder manifest = json.loads((assets / "verification.json").read_text(encoding="utf-8")) @@ -53,7 +53,8 @@ def log_message(self, *a): assert page.get_by_role("heading", name="Try Python components in a notebook", exact=False).is_visible() article = page.locator("article").inner_text() for value in ("Fullbleed 2.6.0a6", "fullbleed.ui.experimental", "no Bootstrap JavaScript", - "class_name", "Document(styles=...)", "Strong", "72-row", "four pages"): + "class_name", "Document(styles=...)", "Strong", "72-row", "four pages", + "Run in local Jupyter", "Run All Cells", "component-order.zip"): assert value in article, value colab = page.get_by_role("link", name="Open in Colab", exact=True).get_attribute("href") source = page.get_by_role("link", name="View the source", exact=True).get_attribute("href") @@ -83,11 +84,14 @@ def log_message(self, *a): page.screenshot(path=str(args.out / f"{label}-preview.png"), animations="disabled") downloads = [] for filename, title in (("order.pdf", "Open the sample PDF"), ("order.styled.html", "Open styled HTML"), - ("order.unstyled.html", "Open unstyled HTML"), ("verification.json", "verification record")): + ("order.unstyled.html", "Open unstyled HTML"), ("verification.json", "verification record"), + ("browser-verification.json", "Jupyter browser record")): link = page.get_by_role("link", name=title, exact=True) response = context.request.get(link.evaluate("a => a.href")) assert response.status == 200, filename assert response.body() == (assets / filename).read_bytes(), filename + if filename == "browser-verification.json": + assert sha256(response.body()).hexdigest() == manifest["browser_verification"]["sha256"] downloads.append({"file": filename, "sha256": sha256(response.body()).hexdigest()}) for filename in ("order.styled.html", "order.unstyled.html"): response = page.goto(base + "/assets/" + folder + "/" + filename, wait_until="networkidle") diff --git a/tools/component-notebook-browser-requirements.txt b/tools/component-notebook-browser-requirements.txt new file mode 100644 index 0000000..29910ec --- /dev/null +++ b/tools/component-notebook-browser-requirements.txt @@ -0,0 +1,3 @@ +-r component-notebook-requirements.txt +notebook==7.6.3 +playwright==1.49.1 diff --git a/tools/verify_component_notebook.py b/tools/verify_component_notebook.py index e2ec547..1fb0fac 100644 --- a/tools/verify_component_notebook.py +++ b/tools/verify_component_notebook.py @@ -28,6 +28,7 @@ "alpha3": ("2.6.0a3", "component-notebook"), "alpha5": ("2.6.0a5", "component-notebook-alpha5"), "alpha6": ("2.6.0a6", "component-notebook-alpha6"), + "alpha6-r1": ("2.6.0a6", "component-notebook-alpha6-r1"), } @@ -235,7 +236,7 @@ def main(): edited.update(number="1051", customer="Moss & Fern ", status="Packed") edited["items"][0]["quantity"] = 32 edited["items"][1]["item"] = "Personalized note cards" - if args.channel in {"alpha5", "alpha6"}: + if args.channel in {"alpha5", "alpha6", "alpha6-r1"}: edited["items"][0]["item"] = "Studio & cards" long = deepcopy(default) long["items"] = [{"item": f"Edition {n:02d} / art print", "quantity": 1, "unit_price": "9.50"} @@ -266,7 +267,7 @@ def main(): destination = out / name destination.mkdir() execute(notebook, work, destination, kernel_dir) - report = verify(work, destination, name, order, accent, version, args.channel in {"alpha5", "alpha6"}) + report = verify(work, destination, name, order, accent, version, args.channel in {"alpha5", "alpha6", "alpha6-r1"}) reports.append(report) print(json.dumps({"case": name, "pages": report["pages"], "ok": True}), flush=True) assert len({r["pdf_sha256"] for r in reports}) == len(reports) From 4f188b8b730ed161d6d3f0b6fbfd95c861a20f9e Mon Sep 17 00:00:00 2001 From: krflol Date: Sun, 18 Oct 2026 11:47:05 -0500 Subject: [PATCH 3/3] Use a YAML block for the notebook browser dependency install --- .github/workflows/docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 33cfecd..6747bf2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -89,7 +89,8 @@ jobs: run: python -I tools/verify_component_notebook.py --channel ${{ matrix.channel }} --check-downloads --out target/component-notebook - name: Install the optional Jupyter browser test tools if: matrix.channel == 'alpha6-r1' - run: python -m pip install --only-binary=:all: -r tools/component-notebook-browser-requirements.txt + run: | + python -m pip install --only-binary=:all: -r tools/component-notebook-browser-requirements.txt - name: Edit, run and download in Jupyter Notebook if: matrix.channel == 'alpha6-r1' run: python -I tools/check_component_notebook_jupyter.py --reference target/component-notebook --out target/component-notebook/browser --browser chrome