diff --git a/.gitattributes b/.gitattributes
index cd5032f..72564f2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -169,3 +169,6 @@ docs/assets/accessible-showcase-alpha7/fonts/*.ttf binary
examples/styling-notebook-alpha10/** text eol=lf
docs/assets/styling-notebook-alpha10/** -text whitespace=cr-at-eol
docs/assets/styling-notebook-alpha10/*.pdf binary
+examples/styling-notebook-alpha11/** text eol=lf
+docs/assets/styling-notebook-alpha11/** -text
+docs/assets/styling-notebook-alpha11/*.pdf binary
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 72dc9fd..e02af63 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -120,7 +120,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
- channel: [alpha9, alpha10]
+ channel: [alpha9, alpha10, alpha11]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
@@ -129,12 +129,12 @@ jobs:
- run: |
python -m pip install --only-binary=:all: -r tools/component-notebook-browser-requirements.txt
- name: Execute the page-band notebook and inspect PDF, HTML and ZIP files
- run: python -I tools/verify_page_band_notebook.py --channel ${{ matrix.channel }} --notebook examples/${{ matrix.channel == 'alpha10' && 'styling-notebook-alpha10/component_styling.ipynb' || 'page-band-notebook-alpha9/component_page_bands.ipynb' }} --out target/page-band-notebook
+ run: python -I tools/verify_page_band_notebook.py --channel ${{ matrix.channel }} --notebook examples/${{ matrix.channel == 'alpha9' && 'page-band-notebook-alpha9/component_page_bands.ipynb' || format('styling-notebook-{0}/component_styling.ipynb', matrix.channel) }} --out target/page-band-notebook
- name: Reproduce the styling notebook and its exact download
- if: matrix.channel == 'alpha10'
+ if: matrix.channel != 'alpha9'
run: |
- python tools/build_styling_notebook.py
- git diff --exit-code -- examples/styling-notebook-alpha10/component_styling.ipynb docs/assets/styling-notebook-alpha10/component_styling.ipynb
+ python tools/build_styling_notebook.py --channel ${{ matrix.channel }}
+ git diff --exit-code -- examples/styling-notebook-${{ matrix.channel }}/component_styling.ipynb docs/assets/styling-notebook-${{ matrix.channel }}/component_styling.ipynb
- name: Check private notebook process cleanup and isolation
run: python -I tools/test_notebook_browser_processes.py
- name: Edit, run and download the page-band notebook in Jupyter
diff --git a/docs/assets/styling-notebook-alpha11/browser-verification.json b/docs/assets/styling-notebook-alpha11/browser-verification.json
new file mode 100644
index 0000000..135b9be
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/browser-verification.json
@@ -0,0 +1,137 @@
+{
+ "ok": true,
+ "checked_at": "2026-10-20T11:32:10.694244+00:00",
+ "notebook_sha256": "9fca59ea5a482560af7d936164660344eb4025b1e895b623640217991ab34494",
+ "runtime_version": "2.6.0a11",
+ "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",
+ "psutil": "7.2.2"
+ },
+ "cases": [
+ {
+ "name": "default",
+ "pages": 3,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "ba5502e74fdc81a3682d769cee3cb747a756a4293d0fb1dacc0cc3d2bb0acbbc",
+ "artifact_hashes": {
+ "field-notes.pdf": "eb8f7c2b4fb5e123feb46a99e0521a7ab04181c0847c675076fe759e2d4bbf94",
+ "field-notes.styled.html": "af5d37e44cd3e038be306e4b84c5f2efe666b9060c10a2d5aa2f5d93275349d6",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "626217670103c45188dd3c1cf96e2a82ef2add3b1bb00baa6dc19fb150eebea9",
+ "field-notes-page-2.png": "4da3c325f07434b01c455b3231e2c63de607a2b7db87a6b09f75a561b92eb925",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ },
+ {
+ "name": "edited",
+ "pages": 3,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "afc7e0c26b524f6c164a140ead83a2692991ef34e2e823757ff6aa62947c2a93",
+ "artifact_hashes": {
+ "field-notes.pdf": "8910ef4bfe885d61fc9b84dfeabd7195b65bafc658259c1f2f983e3b7ccf1a2e",
+ "field-notes.styled.html": "d620a5e4b75ff8e589e117f5baa935640d3fa8ad6f344def9dc8b46e59263f0e",
+ "field-notes.unstyled.html": "2dd7badcfd1b2cbe03523d15724b839365c71087bc694f9adbd599c172f1a486",
+ "field-notes-page-1.png": "9ab4b9593c6c140753e7df1d525e158c134dc29b4f63db5e2cdfd3d871280428",
+ "field-notes-page-2.png": "bb9924fa1ab0b8d1e3f19e7666f1f747030c66815c0c39d64d52cb9b016a3fb5",
+ "field-notes-page-3.png": "addadec6914c032749c6ef68dfd2679975447d5bcd5627721d48cf3cb8933d35"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ },
+ {
+ "name": "selector-only",
+ "pages": 3,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "0502199ceacf99f65e502601de629ced1c04aceb09a59d929061cc8555a78d4c",
+ "artifact_hashes": {
+ "field-notes.pdf": "aa2a67674965407136f6d4ede0f49731969429a60b3a0bb841f61c0870724eb8",
+ "field-notes.styled.html": "b914cceb6f56ee67823abcb3ef98ea26ecb4178d967e0e14c01874d42b4351d4",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "1d4b1926cddd469e051b4b2ab6c9d4989b3640ef5fa532723e1782d728100523",
+ "field-notes-page-2.png": "5c5ecbed1b0958200f3d5262b0fe1836e99c05416e5a876567af0ba13a07b946",
+ "field-notes-page-3.png": "29e2634c27d750c89cd81757289e33be52d7bc8e60e86e6b91f4809da282352a"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ },
+ {
+ "name": "parts-only",
+ "pages": 3,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "6cd8c7655b8c5412b9fd6a4d385e2f6cafafeb1e5ff283e909f3d532b9fee162",
+ "artifact_hashes": {
+ "field-notes.pdf": "54cbc6a1d18f99baa587b7619a74504dca9f110afe1bf6a28d99ffc8148680b9",
+ "field-notes.styled.html": "98ddf2b9e2e3660899f716d80ba5b0800b8def20441e04cc39cb90fb49ff1a9b",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "5967ad012098f123f26594282d0b0a8fb4e631b8b0dd195969ab6d80b8cb72bf",
+ "field-notes-page-2.png": "4da3c325f07434b01c455b3231e2c63de607a2b7db87a6b09f75a561b92eb925",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ },
+ {
+ "name": "long",
+ "pages": 8,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "8dfaf344136dd5648fdbfe66abc979aa3032ca450ea9da19df11545357341723",
+ "artifact_hashes": {
+ "field-notes.pdf": "acd62ae55466277dff26a25c79d151cc2e641bfbbb73f93ab3f74fbbbd45f46c",
+ "field-notes.styled.html": "80d3bfceca08135e21ea69634ead9f1def787b6b526d5f3b9dde1c5d14ee6c3a",
+ "field-notes.unstyled.html": "b0e68ee4983b4cbe3e791654857742bb72e1b5fc2f3e4eaef0766b9235adbd62",
+ "field-notes-page-1.png": "74c2e71cc5c4d32171b5397739d5e4e1da91fbfa15155ff1b4dc8a839cc21c82",
+ "field-notes-page-2.png": "5097d31cc63715cb87f25ddcffe84856cdc328cb0ef950f4792dea4a441d9afe",
+ "field-notes-page-3.png": "79652b15c7a888337c23562cbe0fc65263d921aae45e7755b91e86b309cc2a3e",
+ "field-notes-page-4.png": "b97c3eb1a6cd74f043f86f47cd004ecccc738f36913811ba9c1ea4671decc014",
+ "field-notes-page-5.png": "3e86dd8de2ea14e183b2bdb2aa929ca013403ed4433b1a907cc59b4e009e014a",
+ "field-notes-page-6.png": "7ded0703778250a4ae2b4eedb4bc9d27c59358854f5536cf6b9938f9fa2567ff",
+ "field-notes-page-7.png": "ce4d9ad2c5c676197f766be0952f833d219983aeb1bf93e33464e93f93fa15dd",
+ "field-notes-page-8.png": "50a71a7b28960ffc5aca3707fe4f265b065315b9dd2eab4bb9feeda73b9a6f42"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ },
+ {
+ "name": "empty-after-long",
+ "pages": 3,
+ "actual_browser_download": true,
+ "filename": "component-field-notes.zip",
+ "href_path": "/files/output/component-field-notes.zip",
+ "zip_sha256": "ff74a33651a76874c3b314234e82ec304eb753a13c5f35dafbc699db38fc5d13",
+ "artifact_hashes": {
+ "field-notes.pdf": "114f2076f53ef9e30704b94551c8c8ff6aab8ed56c3d2cb1f6e09e662897b942",
+ "field-notes.styled.html": "1b1d67bcd550c07753f5576c878c76e22a60e62b7c47fb2ff3f3fc6e9c8a4ecb",
+ "field-notes.unstyled.html": "3b93493642feb1c4770622561d0ccf1747edb9134d8e2a25a5246a2dd8b35604",
+ "field-notes-page-1.png": "626217670103c45188dd3c1cf96e2a82ef2add3b1bb00baa6dc19fb150eebea9",
+ "field-notes-page-2.png": "68578aef1e42fbe276c7225322ffd4710398456e5e13c42994b145936680e599",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "matches_independent_reference": true,
+ "visible_previews_match_download": true
+ }
+ ],
+ "stale_previews_excluded": true,
+ "local_install_override": false,
+ "frontend_events": [
+ "TypeError: Cannot read properties of undefined (reading 'schema')\n at http://127.0.0.1:12428/static/notebook/notebook_core.1038f6e3d817cc41.js?v=1038f6e3d817cc41:501:207\n at d (http://127.0.0.1:12428/static/notebook/notebook_core.1038f6e3d817cc41.js?v=1038f6e3d817cc41:509:198736)\n at e.emit (http://127.0.0.1:12428/static/notebook/notebook_core.1038f6e3d817cc41.js?v=1038f6e3d817cc41:509:198469)\n at o.emit (http://127.0.0.1:12428/static/notebook/notebook_core.1038f6e3d817cc41.js?v=1038f6e3d817cc41:509:196456)\n at _.load (http://127.0.0.1:12428/static/notebook/notebook_core.1038f6e3d817cc41.js?v=1038f6e3d817cc41:483:8831)\n at async Promise.all (index 0)"
+ ],
+ "scope": "Local Jupyter Notebook editing, execution, finalized counters, displayed previews and actual browser ZIP downloads. The receipt identifies any local-wheel install override. Hosted Colab is not tested."
+}
diff --git a/docs/assets/styling-notebook-alpha11/component_styling.ipynb b/docs/assets/styling-notebook-alpha11/component_styling.ipynb
new file mode 100644
index 0000000..2fa91f3
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/component_styling.ipynb
@@ -0,0 +1,479 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "intro",
+ "metadata": {},
+ "source": [
+ "# Make the components your own\n",
+ "\n",
+ "Change five styling controls in a complete Common Ground report, then compare\n",
+ "the PDF, styled HTML and unstyled HTML. The document uses Python functions and\n",
+ "component values; its content does not need authored HTML strings.\n",
+ "\n",
+ "| Control | What it demonstrates |\n",
+ "| --- | --- |\n",
+ "| `ACCENT` | A shared color in `Theme`, read with `token(\"accent\")` |\n",
+ "| `TITLE_SIZE` | A reusable `styled(Heading, ...)` primitive |\n",
+ "| `PANEL_BACKGROUND` | `.with_style(...)` on the metrics panel's root |\n",
+ "| `METRIC_COLOR` | `.with_parts(value=...)` on each `Metric` |\n",
+ "| `FOLIO_COLOR` | A `.folio` rule matched by `class_name=\"folio\"` |\n",
+ "\n",
+ "Run all cells, edit one control in step 2, and rerun from there through the\n",
+ "download cell. Change one value at a time to see which elements it affects.\n",
+ "The PDF includes repeating headers and finalized page totals.\n",
+ "\n",
+ "This notebook explicitly installs **Fullbleed 2.6.0a11**. Its component API is\n",
+ "experimental. All content is fictional. Fullbleed and this notebook are MIT\n",
+ "licensed. Use a standard CPU runtime in Colab or local Jupyter and download\n",
+ "your work before leaving the hosted session."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "install-help",
+ "metadata": {},
+ "source": [
+ "## 1. Install the pinned preview\n",
+ "\n",
+ "Fullbleed generates the PDF without a browser or system fonts. The notebook host supplies its display and download interface."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "install-fullbleed",
+ "metadata": {},
+ "source": [
+ "%pip install --quiet --only-binary=:all: \"fullbleed==2.6.0a11\""
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "controls-help",
+ "metadata": {},
+ "source": [
+ "## 2. Make it yours\n",
+ "\n",
+ "Start with the name, headline, accent color, and activity rows. All text values\n",
+ "remain text, including `<` and `&`. After an edit, rerun this cell and every cell\n",
+ "below it. Add activity rows to see the page total change after pagination."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "report-controls",
+ "metadata": {},
+ "source": [
+ "PROJECT_NAME = \"Common Ground\"\n",
+ "HEADLINE = \"A season of shared space.\"\n",
+ "# Five independent styling paths; try changing one at a time.\n",
+ "ACCENT = \"#755122\" # Theme token: the small uppercase labels.\n",
+ "TITLE_SIZE = 31 # styled(): all large report headings, in points.\n",
+ "PANEL_BACKGROUND = \"#edf2e6\" # with_style(): the first-page metrics panel.\n",
+ "METRIC_COLOR = \"#183e34\" # with_parts(): only the large metric values.\n",
+ "FOLIO_COLOR = \"#53665c\" # class_name + selector: the page numbers.\n",
+ "ACTIVITY = [\n",
+ " (\"Garden mornings\", \"12\", \"84 visits\"),\n",
+ " (\"Repair afternoons\", \"4\", \"31 items repaired\"),\n",
+ " (\"Shared meals\", \"3\", \"96 places set\"),\n",
+ " (\"Seed exchanges\", \"2\", \"47 packets shared\"),\n",
+ " (\"Welcome walks\", \"6\", \"28 neighbors\"),\n",
+ "]"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "styles-help",
+ "metadata": {},
+ "source": [
+ "## 3. Give the components a shared style\n",
+ "\n",
+ "`Theme` supplies inherited text and named colors. `styled(...)` creates reusable\n",
+ "defaults. `class_name=\"folio\"` below adds a literal class; the `.folio` selector\n",
+ "in `Document(styles=...)` defines how the page counter looks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "shared-styles",
+ "metadata": {},
+ "source": [
+ "# SPDX-License-Identifier: MIT\n",
+ "\"\"\"A fictional seasonal brief built from reusable component values.\"\"\"\n",
+ "from fullbleed.ui.experimental import (\n",
+ " Box, Column, DataTable, Document, Flow, Heading, Page, PageBand, PageBreak,\n",
+ " PageNumbers, Paragraph, Row, Stack, Strong, Theme, component, mm, pt, style,\n",
+ " styled, token,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "THEME = Theme(\n",
+ " {\"ink\": \"#183e34\", \"muted\": \"#53665c\", \"paper\": \"#edf2e6\", \"accent\": ACCENT},\n",
+ " text=style(font_family=\"Inter\", font_size=pt(10), line_height=1.45, color=token(\"ink\")),\n",
+ ")\n",
+ "Kicker = styled(Paragraph, font_size=pt(7), font_weight=700, letter_spacing=pt(1),\n",
+ " text_transform=\"uppercase\", color=token(\"accent\"))\n",
+ "Title = styled(Heading, font_size=pt(TITLE_SIZE), line_height=1.08, letter_spacing=pt(-0.8))\n",
+ "Small = styled(Paragraph, font_size=pt(8), color=token(\"muted\"))"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "components-help",
+ "metadata": {},
+ "source": [
+ "## 4. Compose reusable pieces\n",
+ "\n",
+ "`Brand`, `Metric`, and `NextStep` return ordinary component trees. Named `part`\n",
+ "values let a caller use `.with_parts(...)` to customize an internal element.\n",
+ "The same `Brand()` value can be used in a page band or in document content."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "reusable-components",
+ "metadata": {},
+ "source": [
+ "@component(parts=(\"name\", \"detail\"))\n",
+ "def Brand():\n",
+ " return Stack(\n",
+ " Paragraph(PROJECT_NAME.upper(), part=\"name\", style=style(font_size=pt(8), font_weight=700, letter_spacing=pt(0.8))),\n",
+ " Paragraph(\"A neighborhood, growing together.\", part=\"detail\", style=style(font_size=pt(6), color=token(\"muted\"))),\n",
+ " gap=pt(2),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@component(parts=(\"value\", \"label\"))\n",
+ "def Metric(value, label):\n",
+ " return Stack(\n",
+ " Paragraph(value, part=\"value\", style=style(font_size=pt(32), font_weight=700, line_height=1.1)),\n",
+ " Small(label, part=\"label\"), gap=pt(5),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@component(parts=(\"heading\", \"body\"))\n",
+ "def NextStep(number, title, text):\n",
+ " return Stack(\n",
+ " Kicker(number),\n",
+ " Heading(title, level=3, part=\"heading\", style=style(font_size=pt(16))),\n",
+ " Paragraph(text, part=\"body\"), gap=pt(6),\n",
+ " )"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "document-help",
+ "metadata": {},
+ "source": [
+ "## 5. Put the report together\n",
+ "\n",
+ "`PageBand` places components in left, center, or right margin slots.\n",
+ "`PageNumbers(total=True)` uses final page counts. Page margins must leave enough\n",
+ "space for the bands; a fit error identifies the page, slot, and authored source.\n",
+ "\n",
+ "The bands repeat as PDF artifacts outside body reading order. Essential identity\n",
+ "and report information also belong in the body.\n",
+ "\n",
+ "The short lesson box uses `break_inside=\"avoid\"` to keep its heading and body\n",
+ "together when a longer activity table pushes it to the next page."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "style-connections",
+ "metadata": {},
+ "source": [
+ "### Follow a style to its target\n",
+ "\n",
+ "`class_name=\"folio\"` on `PageNumbers` emits the class. The matching\n",
+ "`Document(styles={\".folio\": style(...)})` supplies its appearance. The dot\n",
+ "belongs in the selector; omit it from `class_name`. A class name does not\n",
+ "automatically load Bootstrap or any other stylesheet.\n",
+ "\n",
+ "`Metric` explicitly declares the `value` and `label` parts. Its\n",
+ "`.with_parts(value=style(...))` call changes the value without changing the\n",
+ "label. `.with_style(...)` changes a component's root. These calls return new\n",
+ "values, so one instance can be customized without changing its siblings.\n",
+ "\n",
+ "Try `METRIC_COLOR = \"#8c2f6b\"` first. Then restore it and try\n",
+ "`FOLIO_COLOR = \"#206b7a\"`. The named part affects the first page's two numbers;\n",
+ "the selector affects the footer on every page.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "compose-document",
+ "metadata": {},
+ "source": [
+ "def build_document():\n",
+ " activity = ACTIVITY\n",
+ " return Document(\n",
+ " Flow(\n",
+ " Kicker(\"Field notes / Autumn 2026\"),\n",
+ " Title(HEADLINE, level=1),\n",
+ " Paragraph(\"A small record of what happens when neighbors have a place to meet, make, and lend a hand.\"),\n",
+ " Box(\n",
+ " Row(\n",
+ " Metric(\"126\", \"neighbors took part\").with_parts(value=style(color=METRIC_COLOR)),\n",
+ " Metric(\"21\", \"open sessions\").with_parts(value=style(color=METRIC_COLOR)),\n",
+ " gap=pt(18),\n",
+ " ),\n",
+ " style=style(background=token(\"paper\"), padding=pt(18)),\n",
+ " ).with_style(background=PANEL_BACKGROUND),\n",
+ " Heading(\"The place is the invitation\", level=2, style=style(font_size=pt(17))),\n",
+ " Paragraph(\"We kept the gate open, the table long, and the activities simple. New faces became familiar ones. Practical work made room for conversation.\"),\n",
+ " Small(f\"{PROJECT_NAME} is a fictional community project. Every figure in this brief is sample data.\"),\n",
+ " gap=pt(15),\n",
+ " ),\n",
+ " PageBreak(),\n",
+ " Flow(\n",
+ " Kicker(\"01 / What we made possible\"),\n",
+ " Title(\"Small actions. Shared results.\", level=2),\n",
+ " Paragraph(\"The busiest sessions mixed a useful task with time to stay. No specialist knowledge was needed to join in.\"),\n",
+ " DataTable(\n",
+ " activity, caption=\"Season activity\", columns=(\n",
+ " Column(\"Activity\", lambda row: row[0]),\n",
+ " Column(\"Sessions\", lambda row: row[1], align=\"right\", width=mm(18)),\n",
+ " Column(\"Participation\", lambda row: row[2], width=mm(37)),\n",
+ " ),\n",
+ " ).with_parts(\n",
+ " caption=style(font_size=pt(9), font_weight=700, margin_bottom=pt(7)),\n",
+ " header=style(background=token(\"paper\"), font_size=pt(8), color=token(\"ink\")),\n",
+ " cell=style(font_size=pt(8), padding=pt(8)),\n",
+ " ),\n",
+ " Box(\n",
+ " Kicker(\"A useful lesson\"),\n",
+ " Paragraph(Strong(\"Make the first step visible. \"), \"A welcome at the gate, a clearly marked tool bench, and a task that takes ten minutes helped people begin.\"),\n",
+ " style=style(background=token(\"paper\"), padding=pt(15), break_inside=\"avoid\"),\n",
+ " ),\n",
+ " Small(\"Visits and participation counts can include the same neighbor more than once. They describe activity, not unique attendance.\"),\n",
+ " gap=pt(12),\n",
+ " ),\n",
+ " PageBreak(),\n",
+ " Flow(\n",
+ " Kicker(\"02 / What comes next\"),\n",
+ " Title(\"Keep the welcome growing.\", level=2),\n",
+ " Paragraph(\"Three modest commitments will guide the next season. Each has a clear owner and a way for someone new to help.\"),\n",
+ " NextStep(\"01\", \"Make room at the table\", \"Reserve a few places at every shared meal for neighbors joining for the first time. Publish the menu and access information together.\"),\n",
+ " NextStep(\"02\", \"Pass on a practical skill\", \"Pair each repair session with a short demonstration. Keep a simple printed guide beside the tools for people who prefer to follow at their own pace.\"),\n",
+ " NextStep(\"03\", \"Listen before adding more\", \"Ask what made participation easy and what got in the way. Use those answers to choose the next opening hours and activities.\"),\n",
+ " Small(f\"Prepared by {PROJECT_NAME} / October 2026. This sample is authored with Fullbleed components, including its repeated header and finalized page numbers.\"),\n",
+ " gap=pt(15),\n",
+ " ),\n",
+ " title=f\"{PROJECT_NAME} — Field notes\", lang=\"en-US\", theme=THEME,\n",
+ " page=Page(\n",
+ " \"A5\", margin=(mm(20), mm(17), mm(20), mm(17)),\n",
+ " header=PageBand(left=Brand(), right=Kicker(\"Field notes / 03\").with_style(font_size=pt(6))),\n",
+ " footer=PageBand(\n",
+ " left=Small(\"AUTUMN 2026 · SAMPLE DATA\").with_style(font_size=pt(6)),\n",
+ " right=PageNumbers(total=True, prefix=\"\", separator=\" / \", class_name=\"folio\"),\n",
+ " ),\n",
+ " ),\n",
+ " styles={\".folio\": style(font_size=pt(8), color=FOLIO_COLOR)},\n",
+ " )\n",
+ "\n",
+ "\n",
+ "document = build_document()"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "render-help",
+ "metadata": {},
+ "source": [
+ "## 6. Render PDF and export HTML\n",
+ "\n",
+ "The bundled Inter font is registered explicitly for PDF generation. Styled HTML\n",
+ "keeps its font-family declaration; a browser host supplies Inter or a fallback.\n",
+ "Unstyled HTML keeps content and classes while removing generated and inline styles.\n",
+ "Both exports contain one header/main/footer sequence and explicit `[page]` /\n",
+ "`[pages]` placeholders. They do not add JavaScript or browser pagination.\n",
+ "\n",
+ "The PDF/UA-2 profile and source checks express authoring intent. Review and\n",
+ "validate your own output; this notebook does not establish arbitrary-document\n",
+ "accessibility or replace human assistive-technology testing."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "render-export",
+ "metadata": {},
+ "source": [
+ "from importlib import metadata, resources\n",
+ "from pathlib import Path\n",
+ "from fullbleed.ui.experimental import Renderer\n",
+ "\n",
+ "assert metadata.version(\"fullbleed\") == \"2.6.0a11\"\n",
+ "output = Path(\"output\")\n",
+ "output.mkdir(exist_ok=True)\n",
+ "font = resources.files(\"fullbleed_assets\").joinpath(\"fonts/Inter-Variable.ttf\")\n",
+ "renderer = Renderer(\n",
+ " font_files=[str(font)], pdf_profile=\"pdfua2\",\n",
+ " document_timestamp=\"2026-10-19T00:00:00Z\",\n",
+ ")\n",
+ "prepared = document.prepare(renderer, a11y_mode=\"raise\")\n",
+ "pdf_path = output / \"field-notes.pdf\"\n",
+ "pdf_path.write_bytes(prepared.render_pdf())\n",
+ "# Reuse the same content with browser-only presentation rules.\n",
+ "# Flow already exports its browser layout and gaps. Keep font fallbacks\n",
+ "# and screen framing out of the PDF document.\n",
+ "browser_document = Document(\n",
+ " *document.children,\n",
+ " title=document.title, lang=document.lang, page=document.page, theme=document.theme,\n",
+ " styles=document.styles + \"\"\"\n",
+ "@media screen {\n",
+ " body, [data-fb-band-slot] { font-family: Inter, sans-serif; }\n",
+ " body { max-width: 680px; margin: 0 auto; padding: 24px; }\n",
+ " main > [data-fb-flow] { margin-bottom: 40px; }\n",
+ " [data-fb-page-band] { margin: 12px 0 28px; }\n",
+ " footer[data-fb-page-band] { margin-top: 28px; }\n",
+ "}\n",
+ "\"\"\",\n",
+ ")\n",
+ "for include_styles, filename in (\n",
+ " (True, \"field-notes.styled.html\"),\n",
+ " (False, \"field-notes.unstyled.html\"),\n",
+ "):\n",
+ " export_document = browser_document if include_styles else document\n",
+ " (output / filename).write_text(\n",
+ " export_document.to_html(include_styles=include_styles, a11y_mode=\"raise\"),\n",
+ " encoding=\"utf-8\", newline=\"\\n\",\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\"field-notes-page-{number}.png\"\n",
+ " path.write_bytes(png)\n",
+ " preview_paths.append(path)\n",
+ " display(Image(data=png, width=540))\n",
+ "print(f\"{len(preview_paths)} pages. Review every page before using the document.\")"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "download-help",
+ "metadata": {},
+ "source": [
+ "## 7. Download the current result\n",
+ "\n",
+ "The ZIP contains the PDF, both HTML exports, and this run's page previews. In\n",
+ "Colab the next cell starts a download; local Jupyter shows a download link.\n",
+ "The individual files also remain in `output/`. Shorter reruns exclude old,\n",
+ "longer-run previews from the ZIP."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "download",
+ "metadata": {},
+ "source": [
+ "import sys\n",
+ "from zipfile import ZipFile, ZIP_DEFLATED\n",
+ "\n",
+ "bundle_path = output / \"component-field-notes.zip\"\n",
+ "current_files = [pdf_path, output / \"field-notes.styled.html\",\n",
+ " output / \"field-notes.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",
+ "- [Page-band guide and complete source kit](https://docs.fullbleed.dev/guides/component-preview-9/)\n",
+ "- [API, fit diagnostics, and limitations](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a11/docs/component-page-bands.md)\n",
+ "- [Northstar and Common Ground through both authoring paths](https://docs.fullbleed.dev/guides/showcase-authoring/)\n",
+ "- [Share a small authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86)\n",
+ "\n",
+ "Native streaming output is not atomic. Managed fit checks are conservative.\n",
+ "Named or facing-page band variants are not part of this preview. Bootstrap\n",
+ "components and HTML exports remain static."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "export-styling-scope",
+ "metadata": {},
+ "source": [
+ "## Use the HTML in another application\n",
+ "\n",
+ "Both HTML files contain the same authored content. Styled HTML includes the\n",
+ "component and document CSS. Unstyled HTML retains semantic structure and\n",
+ "classes, including `folio`, but contains no style declarations; your host can\n",
+ "provide its own CSS. Both exports are static, without Bootstrap JavaScript.\n",
+ "\n",
+ "The PDF uses explicitly registered, bundled Inter. Registering that font with\n",
+ "`Renderer` does not embed it in browser HTML; the browser host must provide\n",
+ "Inter or use a fallback. HTML has one header/main/footer sequence and\n",
+ "`[page] / [pages]` placeholders. It does not paginate like the PDF.\n",
+ "The included `@media screen` stylesheet supplies a sans-serif fallback and\n",
+ "screen framing. `Flow` exports its own browser layout and gaps; no host\n",
+ "layout rule is needed. PDF-only page breaks are hidden in styled HTML.\n",
+ "The host stylesheet belongs to `browser_document`, which shares the same\n",
+ "component content. The PDF document does not receive those host rules.\n",
+ "\n",
+ "For a longer report, put 72 rows in `ACTIVITY` and rerun. Use `ACTIVITY = []`\n",
+ "afterwards to check the empty state. The ZIP includes only the current PDF,\n",
+ "HTML files and previews, even after a longer run.\n",
+ "\n",
+ "The PDF/UA-2 profile and source checks express authoring intent. Validate your\n",
+ "own output and perform human assistive-technology review when needed. Repeated\n",
+ "PDF bands sit outside body reading order; essential information also belongs\n",
+ "in the body.\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ },
+ "colab": {
+ "name": "component_page_bands.ipynb",
+ "provenance": []
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/assets/styling-notebook-alpha11/default-page-1.png b/docs/assets/styling-notebook-alpha11/default-page-1.png
new file mode 100644
index 0000000..0911adc
Binary files /dev/null and b/docs/assets/styling-notebook-alpha11/default-page-1.png differ
diff --git a/docs/assets/styling-notebook-alpha11/edited-page-1.png b/docs/assets/styling-notebook-alpha11/edited-page-1.png
new file mode 100644
index 0000000..f8bf89b
Binary files /dev/null and b/docs/assets/styling-notebook-alpha11/edited-page-1.png differ
diff --git a/docs/assets/styling-notebook-alpha11/edited.pdf b/docs/assets/styling-notebook-alpha11/edited.pdf
new file mode 100644
index 0000000..91e55c5
Binary files /dev/null and b/docs/assets/styling-notebook-alpha11/edited.pdf differ
diff --git a/docs/assets/styling-notebook-alpha11/field-notes.pdf b/docs/assets/styling-notebook-alpha11/field-notes.pdf
new file mode 100644
index 0000000..f280079
Binary files /dev/null and b/docs/assets/styling-notebook-alpha11/field-notes.pdf differ
diff --git a/docs/assets/styling-notebook-alpha11/field-notes.styled.html b/docs/assets/styling-notebook-alpha11/field-notes.styled.html
new file mode 100644
index 0000000..f5a9a31
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/field-notes.styled.html
@@ -0,0 +1,36 @@
+
Common Ground — Field notesCOMMON GROUND
A neighborhood, growing together.
Field notes / Autumn 2026
A season of shared space.
A small record of what happens when neighbors have a place to meet, make, and lend a hand.
The place is the invitation
We kept the gate open, the table long, and the activities simple. New faces became familiar ones. Practical work made room for conversation.
Common Ground is a fictional community project. Every figure in this brief is sample data.
01 / What we made possible
Small actions. Shared results.
The busiest sessions mixed a useful task with time to stay. No specialist knowledge was needed to join in.
Season activity| Activity | Sessions | Participation |
|---|
| Garden mornings | 12 | 84 visits |
| Repair afternoons | 4 | 31 items repaired |
| Shared meals | 3 | 96 places set |
| Seed exchanges | 2 | 47 packets shared |
| Welcome walks | 6 | 28 neighbors |
A useful lesson
Make the first step visible. A welcome at the gate, a clearly marked tool bench, and a task that takes ten minutes helped people begin.
Visits and participation counts can include the same neighbor more than once. They describe activity, not unique attendance.
02 / What comes next
Keep the welcome growing.
Three modest commitments will guide the next season. Each has a clear owner and a way for someone new to help.
01
Make room at the table
Reserve a few places at every shared meal for neighbors joining for the first time. Publish the menu and access information together.
02
Pass on a practical skill
Pair each repair session with a short demonstration. Keep a simple printed guide beside the tools for people who prefer to follow at their own pace.
03
Listen before adding more
Ask what made participation easy and what got in the way. Use those answers to choose the next opening hours and activities.
Prepared by Common Ground / October 2026. This sample is authored with Fullbleed components, including its repeated header and finalized page numbers.
\ No newline at end of file
diff --git a/docs/assets/styling-notebook-alpha11/field-notes.unstyled.html b/docs/assets/styling-notebook-alpha11/field-notes.unstyled.html
new file mode 100644
index 0000000..39a60c1
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/field-notes.unstyled.html
@@ -0,0 +1 @@
+Common Ground — Field notesCOMMON GROUND
A neighborhood, growing together.
Field notes / Autumn 2026
A season of shared space.
A small record of what happens when neighbors have a place to meet, make, and lend a hand.
The place is the invitation
We kept the gate open, the table long, and the activities simple. New faces became familiar ones. Practical work made room for conversation.
Common Ground is a fictional community project. Every figure in this brief is sample data.
01 / What we made possible
Small actions. Shared results.
The busiest sessions mixed a useful task with time to stay. No specialist knowledge was needed to join in.
Season activity| Activity | Sessions | Participation |
|---|
| Garden mornings | 12 | 84 visits |
| Repair afternoons | 4 | 31 items repaired |
| Shared meals | 3 | 96 places set |
| Seed exchanges | 2 | 47 packets shared |
| Welcome walks | 6 | 28 neighbors |
A useful lesson
Make the first step visible. A welcome at the gate, a clearly marked tool bench, and a task that takes ten minutes helped people begin.
Visits and participation counts can include the same neighbor more than once. They describe activity, not unique attendance.
02 / What comes next
Keep the welcome growing.
Three modest commitments will guide the next season. Each has a clear owner and a way for someone new to help.
01
Make room at the table
Reserve a few places at every shared meal for neighbors joining for the first time. Publish the menu and access information together.
02
Pass on a practical skill
Pair each repair session with a short demonstration. Keep a simple printed guide beside the tools for people who prefer to follow at their own pace.
03
Listen before adding more
Ask what made participation easy and what got in the way. Use those answers to choose the next opening hours and activities.
Prepared by Common Ground / October 2026. This sample is authored with Fullbleed components, including its repeated header and finalized page numbers.
\ No newline at end of file
diff --git a/docs/assets/styling-notebook-alpha11/style-isolation.json b/docs/assets/styling-notebook-alpha11/style-isolation.json
new file mode 100644
index 0000000..ad19440
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/style-isolation.json
@@ -0,0 +1,36 @@
+{
+ "ok": true,
+ "cases": {
+ "selector-only": [
+ [
+ 543,
+ 860,
+ 567,
+ 872
+ ],
+ [
+ 541,
+ 860,
+ 567,
+ 872
+ ],
+ [
+ 541,
+ 860,
+ 567,
+ 872
+ ]
+ ],
+ "parts-only": [
+ [
+ 102,
+ 352,
+ 381,
+ 391
+ ],
+ null,
+ null
+ ]
+ },
+ "scope": "Selector-only changes stay in the footer on every page; named-part-only changes leave pages 2 and 3 identical."
+}
diff --git a/docs/assets/styling-notebook-alpha11/verification.json b/docs/assets/styling-notebook-alpha11/verification.json
new file mode 100644
index 0000000..ea1eead
--- /dev/null
+++ b/docs/assets/styling-notebook-alpha11/verification.json
@@ -0,0 +1,918 @@
+{
+ "ok": true,
+ "notebook": "examples/styling-notebook-alpha11/component_styling.ipynb",
+ "notebook_sha256": "9fca59ea5a482560af7d936164660344eb4025b1e895b623640217991ab34494",
+ "runtime_version": "2.6.0a11",
+ "install_cell_executed": true,
+ "public_install_cell": true,
+ "local_install_override": false,
+ "cases": [
+ {
+ "name": "default",
+ "pages": 3,
+ "row_count": 5,
+ "controls": {
+ "PROJECT_NAME": "Common Ground",
+ "HEADLINE": "A season of shared space.",
+ "ACCENT": "#755122",
+ "TITLE_SIZE": 31,
+ "PANEL_BACKGROUND": "#edf2e6",
+ "METRIC_COLOR": "#183e34",
+ "FOLIO_COLOR": "#53665c",
+ "ACTIVITY": [
+ [
+ "Garden mornings",
+ "12",
+ "84 visits"
+ ],
+ [
+ "Repair afternoons",
+ "4",
+ "31 items repaired"
+ ],
+ [
+ "Shared meals",
+ "3",
+ "96 places set"
+ ],
+ [
+ "Seed exchanges",
+ "2",
+ "47 packets shared"
+ ],
+ [
+ "Welcome walks",
+ "6",
+ "28 neighbors"
+ ]
+ ]
+ },
+ "pdf_sha256": "eb8f7c2b4fb5e123feb46a99e0521a7ab04181c0847c675076fe759e2d4bbf94",
+ "text_markers": [
+ "Common Ground",
+ "A season of shared space.",
+ "Keep the welcome growing.",
+ "Garden mornings",
+ "Repair afternoons",
+ "Shared meals",
+ "Seed exchanges",
+ "Welcome walks"
+ ],
+ "font_count": 1,
+ "accent": "#755122",
+ "accent_pixels": 2138,
+ "independent_char_bounds_checked": 1807,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "eb8f7c2b4fb5e123feb46a99e0521a7ab04181c0847c675076fe759e2d4bbf94",
+ "field-notes.styled.html": "af5d37e44cd3e038be306e4b84c5f2efe666b9060c10a2d5aa2f5d93275349d6",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "626217670103c45188dd3c1cf96e2a82ef2add3b1bb00baa6dc19fb150eebea9",
+ "field-notes-page-2.png": "4da3c325f07434b01c455b3231e2c63de607a2b7db87a6b09f75a561b92eb925",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 138502,
+ "METRIC_COLOR": 24023,
+ "FOLIO_COLOR": 19
+ },
+ "qualified_field_notes_pdf_reproduced": true
+ },
+ {
+ "name": "edited",
+ "pages": 3,
+ "row_count": 5,
+ "controls": {
+ "PROJECT_NAME": "River ",
+ "HEADLINE": "A place to meet & make.",
+ "ACCENT": "#763c96",
+ "TITLE_SIZE": 27,
+ "PANEL_BACKGROUND": "#f1e8f5",
+ "METRIC_COLOR": "#8c2f6b",
+ "FOLIO_COLOR": "#206b7a",
+ "ACTIVITY": [
+ [
+ "Studio & cards",
+ "16",
+ "89 visits"
+ ],
+ [
+ "Repair afternoons",
+ "4",
+ "31 items repaired"
+ ],
+ [
+ "Shared meals",
+ "3",
+ "96 places set"
+ ],
+ [
+ "Seed exchanges",
+ "2",
+ "47 packets shared"
+ ],
+ [
+ "Welcome walks",
+ "6",
+ "28 neighbors"
+ ]
+ ]
+ },
+ "pdf_sha256": "8910ef4bfe885d61fc9b84dfeabd7195b65bafc658259c1f2f983e3b7ccf1a2e",
+ "text_markers": [
+ "River ",
+ "A place to meet & make.",
+ "Keep the welcome growing.",
+ "Studio & cards",
+ "Repair afternoons",
+ "Shared meals",
+ "Seed exchanges",
+ "Welcome walks"
+ ],
+ "font_count": 1,
+ "accent": "#763c96",
+ "accent_pixels": 2158,
+ "independent_char_bounds_checked": 1818,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "8910ef4bfe885d61fc9b84dfeabd7195b65bafc658259c1f2f983e3b7ccf1a2e",
+ "field-notes.styled.html": "d620a5e4b75ff8e589e117f5baa935640d3fa8ad6f344def9dc8b46e59263f0e",
+ "field-notes.unstyled.html": "2dd7badcfd1b2cbe03523d15724b839365c71087bc694f9adbd599c172f1a486",
+ "field-notes-page-1.png": "9ab4b9593c6c140753e7df1d525e158c134dc29b4f63db5e2cdfd3d871280428",
+ "field-notes-page-2.png": "bb9924fa1ab0b8d1e3f19e7666f1f747030c66815c0c39d64d52cb9b016a3fb5",
+ "field-notes-page-3.png": "addadec6914c032749c6ef68dfd2679975447d5bcd5627721d48cf3cb8933d35"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 62964,
+ "METRIC_COLOR": 1492,
+ "FOLIO_COLOR": 4
+ }
+ },
+ {
+ "name": "selector-only",
+ "pages": 3,
+ "row_count": 5,
+ "controls": {
+ "PROJECT_NAME": "Common Ground",
+ "HEADLINE": "A season of shared space.",
+ "ACCENT": "#755122",
+ "TITLE_SIZE": 31,
+ "PANEL_BACKGROUND": "#edf2e6",
+ "METRIC_COLOR": "#183e34",
+ "FOLIO_COLOR": "#206b7a",
+ "ACTIVITY": [
+ [
+ "Garden mornings",
+ "12",
+ "84 visits"
+ ],
+ [
+ "Repair afternoons",
+ "4",
+ "31 items repaired"
+ ],
+ [
+ "Shared meals",
+ "3",
+ "96 places set"
+ ],
+ [
+ "Seed exchanges",
+ "2",
+ "47 packets shared"
+ ],
+ [
+ "Welcome walks",
+ "6",
+ "28 neighbors"
+ ]
+ ]
+ },
+ "pdf_sha256": "aa2a67674965407136f6d4ede0f49731969429a60b3a0bb841f61c0870724eb8",
+ "text_markers": [
+ "Common Ground",
+ "A season of shared space.",
+ "Keep the welcome growing.",
+ "Garden mornings",
+ "Repair afternoons",
+ "Shared meals",
+ "Seed exchanges",
+ "Welcome walks"
+ ],
+ "font_count": 1,
+ "accent": "#755122",
+ "accent_pixels": 2138,
+ "independent_char_bounds_checked": 1807,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "aa2a67674965407136f6d4ede0f49731969429a60b3a0bb841f61c0870724eb8",
+ "field-notes.styled.html": "b914cceb6f56ee67823abcb3ef98ea26ecb4178d967e0e14c01874d42b4351d4",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "1d4b1926cddd469e051b4b2ab6c9d4989b3640ef5fa532723e1782d728100523",
+ "field-notes-page-2.png": "5c5ecbed1b0958200f3d5262b0fe1836e99c05416e5a876567af0ba13a07b946",
+ "field-notes-page-3.png": "29e2634c27d750c89cd81757289e33be52d7bc8e60e86e6b91f4809da282352a"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 138502,
+ "METRIC_COLOR": 24023,
+ "FOLIO_COLOR": 4
+ }
+ },
+ {
+ "name": "parts-only",
+ "pages": 3,
+ "row_count": 5,
+ "controls": {
+ "PROJECT_NAME": "Common Ground",
+ "HEADLINE": "A season of shared space.",
+ "ACCENT": "#755122",
+ "TITLE_SIZE": 31,
+ "PANEL_BACKGROUND": "#edf2e6",
+ "METRIC_COLOR": "#8c2f6b",
+ "FOLIO_COLOR": "#53665c",
+ "ACTIVITY": [
+ [
+ "Garden mornings",
+ "12",
+ "84 visits"
+ ],
+ [
+ "Repair afternoons",
+ "4",
+ "31 items repaired"
+ ],
+ [
+ "Shared meals",
+ "3",
+ "96 places set"
+ ],
+ [
+ "Seed exchanges",
+ "2",
+ "47 packets shared"
+ ],
+ [
+ "Welcome walks",
+ "6",
+ "28 neighbors"
+ ]
+ ]
+ },
+ "pdf_sha256": "54cbc6a1d18f99baa587b7619a74504dca9f110afe1bf6a28d99ffc8148680b9",
+ "text_markers": [
+ "Common Ground",
+ "A season of shared space.",
+ "Keep the welcome growing.",
+ "Garden mornings",
+ "Repair afternoons",
+ "Shared meals",
+ "Seed exchanges",
+ "Welcome walks"
+ ],
+ "font_count": 1,
+ "accent": "#755122",
+ "accent_pixels": 2138,
+ "independent_char_bounds_checked": 1807,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "54cbc6a1d18f99baa587b7619a74504dca9f110afe1bf6a28d99ffc8148680b9",
+ "field-notes.styled.html": "98ddf2b9e2e3660899f716d80ba5b0800b8def20441e04cc39cb90fb49ff1a9b",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "field-notes-page-1.png": "5967ad012098f123f26594282d0b0a8fb4e631b8b0dd195969ab6d80b8cb72bf",
+ "field-notes-page-2.png": "4da3c325f07434b01c455b3231e2c63de607a2b7db87a6b09f75a561b92eb925",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 138502,
+ "METRIC_COLOR": 1410,
+ "FOLIO_COLOR": 19
+ }
+ },
+ {
+ "name": "long",
+ "pages": 8,
+ "row_count": 72,
+ "controls": {
+ "PROJECT_NAME": "Common Ground",
+ "HEADLINE": "A season of shared space.",
+ "ACCENT": "#755122",
+ "TITLE_SIZE": 31,
+ "PANEL_BACKGROUND": "#edf2e6",
+ "METRIC_COLOR": "#183e34",
+ "FOLIO_COLOR": "#53665c",
+ "ACTIVITY": [
+ [
+ "Session 01",
+ "1",
+ "8 neighbors"
+ ],
+ [
+ "Session 02",
+ "2",
+ "8 neighbors"
+ ],
+ [
+ "Session 03",
+ "3",
+ "8 neighbors"
+ ],
+ [
+ "Session 04",
+ "4",
+ "8 neighbors"
+ ],
+ [
+ "Session 05",
+ "5",
+ "8 neighbors"
+ ],
+ [
+ "Session 06",
+ "6",
+ "8 neighbors"
+ ],
+ [
+ "Session 07",
+ "7",
+ "8 neighbors"
+ ],
+ [
+ "Session 08",
+ "8",
+ "8 neighbors"
+ ],
+ [
+ "Session 09",
+ "9",
+ "8 neighbors"
+ ],
+ [
+ "Session 10",
+ "10",
+ "8 neighbors"
+ ],
+ [
+ "Session 11",
+ "11",
+ "8 neighbors"
+ ],
+ [
+ "Session 12",
+ "12",
+ "8 neighbors"
+ ],
+ [
+ "Session 13",
+ "13",
+ "8 neighbors"
+ ],
+ [
+ "Session 14",
+ "14",
+ "8 neighbors"
+ ],
+ [
+ "Session 15",
+ "15",
+ "8 neighbors"
+ ],
+ [
+ "Session 16",
+ "16",
+ "8 neighbors"
+ ],
+ [
+ "Session 17",
+ "17",
+ "8 neighbors"
+ ],
+ [
+ "Session 18",
+ "18",
+ "8 neighbors"
+ ],
+ [
+ "Session 19",
+ "19",
+ "8 neighbors"
+ ],
+ [
+ "Session 20",
+ "20",
+ "8 neighbors"
+ ],
+ [
+ "Session 21",
+ "21",
+ "8 neighbors"
+ ],
+ [
+ "Session 22",
+ "22",
+ "8 neighbors"
+ ],
+ [
+ "Session 23",
+ "23",
+ "8 neighbors"
+ ],
+ [
+ "Session 24",
+ "24",
+ "8 neighbors"
+ ],
+ [
+ "Session 25",
+ "25",
+ "8 neighbors"
+ ],
+ [
+ "Session 26",
+ "26",
+ "8 neighbors"
+ ],
+ [
+ "Session 27",
+ "27",
+ "8 neighbors"
+ ],
+ [
+ "Session 28",
+ "28",
+ "8 neighbors"
+ ],
+ [
+ "Session 29",
+ "29",
+ "8 neighbors"
+ ],
+ [
+ "Session 30",
+ "30",
+ "8 neighbors"
+ ],
+ [
+ "Session 31",
+ "31",
+ "8 neighbors"
+ ],
+ [
+ "Session 32",
+ "32",
+ "8 neighbors"
+ ],
+ [
+ "Session 33",
+ "33",
+ "8 neighbors"
+ ],
+ [
+ "Session 34",
+ "34",
+ "8 neighbors"
+ ],
+ [
+ "Session 35",
+ "35",
+ "8 neighbors"
+ ],
+ [
+ "Session 36",
+ "36",
+ "8 neighbors"
+ ],
+ [
+ "Session 37",
+ "37",
+ "8 neighbors"
+ ],
+ [
+ "Session 38",
+ "38",
+ "8 neighbors"
+ ],
+ [
+ "Session 39",
+ "39",
+ "8 neighbors"
+ ],
+ [
+ "Session 40",
+ "40",
+ "8 neighbors"
+ ],
+ [
+ "Session 41",
+ "41",
+ "8 neighbors"
+ ],
+ [
+ "Session 42",
+ "42",
+ "8 neighbors"
+ ],
+ [
+ "Session 43",
+ "43",
+ "8 neighbors"
+ ],
+ [
+ "Session 44",
+ "44",
+ "8 neighbors"
+ ],
+ [
+ "Session 45",
+ "45",
+ "8 neighbors"
+ ],
+ [
+ "Session 46",
+ "46",
+ "8 neighbors"
+ ],
+ [
+ "Session 47",
+ "47",
+ "8 neighbors"
+ ],
+ [
+ "Session 48",
+ "48",
+ "8 neighbors"
+ ],
+ [
+ "Session 49",
+ "49",
+ "8 neighbors"
+ ],
+ [
+ "Session 50",
+ "50",
+ "8 neighbors"
+ ],
+ [
+ "Session 51",
+ "51",
+ "8 neighbors"
+ ],
+ [
+ "Session 52",
+ "52",
+ "8 neighbors"
+ ],
+ [
+ "Session 53",
+ "53",
+ "8 neighbors"
+ ],
+ [
+ "Session 54",
+ "54",
+ "8 neighbors"
+ ],
+ [
+ "Session 55",
+ "55",
+ "8 neighbors"
+ ],
+ [
+ "Session 56",
+ "56",
+ "8 neighbors"
+ ],
+ [
+ "Session 57",
+ "57",
+ "8 neighbors"
+ ],
+ [
+ "Session 58",
+ "58",
+ "8 neighbors"
+ ],
+ [
+ "Session 59",
+ "59",
+ "8 neighbors"
+ ],
+ [
+ "Session 60",
+ "60",
+ "8 neighbors"
+ ],
+ [
+ "Session 61",
+ "61",
+ "8 neighbors"
+ ],
+ [
+ "Session 62",
+ "62",
+ "8 neighbors"
+ ],
+ [
+ "Session 63",
+ "63",
+ "8 neighbors"
+ ],
+ [
+ "Session 64",
+ "64",
+ "8 neighbors"
+ ],
+ [
+ "Session 65",
+ "65",
+ "8 neighbors"
+ ],
+ [
+ "Session 66",
+ "66",
+ "8 neighbors"
+ ],
+ [
+ "Session 67",
+ "67",
+ "8 neighbors"
+ ],
+ [
+ "Session 68",
+ "68",
+ "8 neighbors"
+ ],
+ [
+ "Session 69",
+ "69",
+ "8 neighbors"
+ ],
+ [
+ "Session 70",
+ "70",
+ "8 neighbors"
+ ],
+ [
+ "Session 71",
+ "71",
+ "8 neighbors"
+ ],
+ [
+ "Session 72",
+ "72",
+ "8 neighbors"
+ ]
+ ]
+ },
+ "pdf_sha256": "acd62ae55466277dff26a25c79d151cc2e641bfbbb73f93ab3f74fbbbd45f46c",
+ "text_markers": [
+ "Common Ground",
+ "A season of shared space.",
+ "Keep the welcome growing.",
+ "Session 01",
+ "Session 02",
+ "Session 03",
+ "Session 04",
+ "Session 05",
+ "Session 06",
+ "Session 07",
+ "Session 08",
+ "Session 09",
+ "Session 10",
+ "Session 11",
+ "Session 12",
+ "Session 13",
+ "Session 14",
+ "Session 15",
+ "Session 16",
+ "Session 17",
+ "Session 18",
+ "Session 19",
+ "Session 20",
+ "Session 21",
+ "Session 22",
+ "Session 23",
+ "Session 24",
+ "Session 25",
+ "Session 26",
+ "Session 27",
+ "Session 28",
+ "Session 29",
+ "Session 30",
+ "Session 31",
+ "Session 32",
+ "Session 33",
+ "Session 34",
+ "Session 35",
+ "Session 36",
+ "Session 37",
+ "Session 38",
+ "Session 39",
+ "Session 40",
+ "Session 41",
+ "Session 42",
+ "Session 43",
+ "Session 44",
+ "Session 45",
+ "Session 46",
+ "Session 47",
+ "Session 48",
+ "Session 49",
+ "Session 50",
+ "Session 51",
+ "Session 52",
+ "Session 53",
+ "Session 54",
+ "Session 55",
+ "Session 56",
+ "Session 57",
+ "Session 58",
+ "Session 59",
+ "Session 60",
+ "Session 61",
+ "Session 62",
+ "Session 63",
+ "Session 64",
+ "Session 65",
+ "Session 66",
+ "Session 67",
+ "Session 68",
+ "Session 69",
+ "Session 70",
+ "Session 71",
+ "Session 72"
+ ],
+ "font_count": 1,
+ "accent": "#755122",
+ "accent_pixels": 3109,
+ "independent_char_bounds_checked": 3689,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png",
+ "field-notes-page-4.png",
+ "field-notes-page-5.png",
+ "field-notes-page-6.png",
+ "field-notes-page-7.png",
+ "field-notes-page-8.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "acd62ae55466277dff26a25c79d151cc2e641bfbbb73f93ab3f74fbbbd45f46c",
+ "field-notes.styled.html": "80d3bfceca08135e21ea69634ead9f1def787b6b526d5f3b9dde1c5d14ee6c3a",
+ "field-notes.unstyled.html": "b0e68ee4983b4cbe3e791654857742bb72e1b5fc2f3e4eaef0766b9235adbd62",
+ "field-notes-page-1.png": "74c2e71cc5c4d32171b5397739d5e4e1da91fbfa15155ff1b4dc8a839cc21c82",
+ "field-notes-page-2.png": "5097d31cc63715cb87f25ddcffe84856cdc328cb0ef950f4792dea4a441d9afe",
+ "field-notes-page-3.png": "79652b15c7a888337c23562cbe0fc65263d921aae45e7755b91e86b309cc2a3e",
+ "field-notes-page-4.png": "b97c3eb1a6cd74f043f86f47cd004ecccc738f36913811ba9c1ea4671decc014",
+ "field-notes-page-5.png": "3e86dd8de2ea14e183b2bdb2aa929ca013403ed4433b1a907cc59b4e009e014a",
+ "field-notes-page-6.png": "7ded0703778250a4ae2b4eedb4bc9d27c59358854f5536cf6b9938f9fa2567ff",
+ "field-notes-page-7.png": "ce4d9ad2c5c676197f766be0952f833d219983aeb1bf93e33464e93f93fa15dd",
+ "field-notes-page-8.png": "50a71a7b28960ffc5aca3707fe4f265b065315b9dd2eab4bb9feeda73b9a6f42"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 218545,
+ "METRIC_COLOR": 26454,
+ "FOLIO_COLOR": 19
+ }
+ },
+ {
+ "name": "empty-after-long",
+ "pages": 3,
+ "row_count": 0,
+ "controls": {
+ "PROJECT_NAME": "Common Ground",
+ "HEADLINE": "A season of shared space.",
+ "ACCENT": "#755122",
+ "TITLE_SIZE": 31,
+ "PANEL_BACKGROUND": "#edf2e6",
+ "METRIC_COLOR": "#183e34",
+ "FOLIO_COLOR": "#53665c",
+ "ACTIVITY": []
+ },
+ "pdf_sha256": "114f2076f53ef9e30704b94551c8c8ff6aab8ed56c3d2cb1f6e09e662897b942",
+ "text_markers": [
+ "Common Ground",
+ "A season of shared space.",
+ "Keep the welcome growing."
+ ],
+ "font_count": 1,
+ "accent": "#755122",
+ "accent_pixels": 2137,
+ "independent_char_bounds_checked": 1685,
+ "repeat_pdf_identical": true,
+ "direct_pdf_identical": true,
+ "zip_files": [
+ "field-notes.pdf",
+ "field-notes.styled.html",
+ "field-notes.unstyled.html",
+ "field-notes-page-1.png",
+ "field-notes-page-2.png",
+ "field-notes-page-3.png"
+ ],
+ "styled_and_unstyled_html": true,
+ "finalized_page_counts": true,
+ "callout_kept_together": true,
+ "artifact_hashes": {
+ "field-notes.pdf": "114f2076f53ef9e30704b94551c8c8ff6aab8ed56c3d2cb1f6e09e662897b942",
+ "field-notes.styled.html": "1b1d67bcd550c07753f5576c878c76e22a60e62b7c47fb2ff3f3fc6e9c8a4ecb",
+ "field-notes.unstyled.html": "3b93493642feb1c4770622561d0ccf1747edb9134d8e2a25a5246a2dd8b35604",
+ "field-notes-page-1.png": "626217670103c45188dd3c1cf96e2a82ef2add3b1bb00baa6dc19fb150eebea9",
+ "field-notes-page-2.png": "68578aef1e42fbe276c7225322ffd4710398456e5e13c42994b145936680e599",
+ "field-notes-page-3.png": "b3ddbd6538cf7e9f7109ca8b185dcc56f4e778808ae38ea9a18582628a432a16"
+ },
+ "styling_pixels": {
+ "PANEL_BACKGROUND": 138543,
+ "METRIC_COLOR": 24019,
+ "FOLIO_COLOR": 19
+ }
+ }
+ ],
+ "stale_previews_excluded": true,
+ "local_wheel": null,
+ "checked_at": "2026-10-20T11:28:23.594995+00:00",
+ "scope": "Jupyter kernel execution, independent saved-PDF inspection, repeat rendering, HTML semantics, and ZIP contents. Browser downloads and hosted Colab are separate checks.",
+ "downloads": {
+ "field-notes.pdf": "eb8f7c2b4fb5e123feb46a99e0521a7ab04181c0847c675076fe759e2d4bbf94",
+ "edited.pdf": "8910ef4bfe885d61fc9b84dfeabd7195b65bafc658259c1f2f983e3b7ccf1a2e",
+ "field-notes.styled.html": "af5d37e44cd3e038be306e4b84c5f2efe666b9060c10a2d5aa2f5d93275349d6",
+ "field-notes.unstyled.html": "7fc4b0bef4fe94eea9e54230db9bf68a17e1c1049ff8c86b23013ac6baa688ac",
+ "default-page-1.png": "626217670103c45188dd3c1cf96e2a82ef2add3b1bb00baa6dc19fb150eebea9",
+ "edited-page-1.png": "9ab4b9593c6c140753e7df1d525e158c134dc29b4f63db5e2cdfd3d871280428",
+ "style-isolation.json": "b364aed0f3a8b7a780ec0054780e67935e540fecd530b8c5fb2a3566823fa4f1",
+ "browser-verification.json": "387c040979748f78ab9b8d55d05db39fd7c6c59e334c90af7c41d40e02b2968a"
+ },
+ "browser_verification": {
+ "path": "browser-verification.json",
+ "sha256": "387c040979748f78ab9b8d55d05db39fd7c6c59e334c90af7c41d40e02b2968a"
+ },
+ "visual_review_pending": false,
+ "visual_review": {
+ "cases": 6,
+ "pages": 23,
+ "prior_pdf_and_preview_bytes_preserved": true,
+ "findings": "The six public-package PDFs and 23 previews match the reviewed local and alpha 10 specimens. Both HTML exports match the locally reviewed alpha 11 documents."
+ },
+ "public_wheel": {
+ "file": "fullbleed-2.6.0a11-cp310-abi3-win_amd64.whl",
+ "url": "https://files.pythonhosted.org/packages/d7/51/63e36d555f750188705ec0b62c46e991d3813c7b535662b5fc2a98350ec7/fullbleed-2.6.0a11-cp310-abi3-win_amd64.whl",
+ "bytes": 8041623,
+ "sha256": "18fae16dcfeef0c6b1083489bbacd02eca40a7fd150c5f39b75b7a9dd406c3d5",
+ "kind": "wheel"
+ },
+ "publication_state": "public package and actual Jupyter execution verified"
+}
diff --git a/docs/getting-started/component-notebook.md b/docs/getting-started/component-notebook.md
index 77752ad..c70abde 100644
--- a/docs/getting-started/component-notebook.md
+++ b/docs/getting-started/component-notebook.md
@@ -5,8 +5,8 @@ description: Edit order data and component styles in Colab or Jupyter, then down
# Try Python components in a notebook
-!!! info "Current component preview: 2.6.0a10"
- [Get the current showcase source kit and verified PDFs](../guides/component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current showcase source kit and verified PDFs](../guides/component-preview-11.md).
[Try the new page-band notebook](page-band-notebook.md) for reusable headers and final page totals.
The downloadable kits on this page retain their original version pins and evidence.
diff --git a/docs/getting-started/component-styling.md b/docs/getting-started/component-styling.md
index 9dbc223..b98e9ee 100644
--- a/docs/getting-started/component-styling.md
+++ b/docs/getting-started/component-styling.md
@@ -10,22 +10,22 @@ elements change, then download the PDF, styled HTML and unstyled HTML.
The notebook includes every component definition and uses Python values for
the content.
-[Open in Colab](https://colab.research.google.com/github/fullbleed-engine/docs/blob/54ebe5e4e89d3a7673a4a09b566924db5b3e6fb9/examples/styling-notebook-alpha10/component_styling.ipynb){ .md-button .md-button--primary }
-[Download the notebook](../assets/styling-notebook-alpha10/component_styling.ipynb){ .md-button download="component_styling.ipynb" }
-[View the pinned source](https://github.com/fullbleed-engine/docs/blob/54ebe5e4e89d3a7673a4a09b566924db5b3e6fb9/examples/styling-notebook-alpha10/component_styling.ipynb){ .md-button }
+[Open in Colab](https://colab.research.google.com/github/fullbleed-engine/docs/blob/70fd19a7f292ce52bfb466ed782d4c4776a68f12/examples/styling-notebook-alpha11/component_styling.ipynb){ .md-button .md-button--primary }
+[Download the notebook](../assets/styling-notebook-alpha11/component_styling.ipynb){ .md-button download="component_styling.ipynb" }
+[View the pinned source](https://github.com/fullbleed-engine/docs/blob/70fd19a7f292ce52bfb466ed782d4c4776a68f12/examples/styling-notebook-alpha11/component_styling.ipynb){ .md-button }
-!!! info "Opt-in component preview: 2.6.0a10"
- The first cell installs `fullbleed==2.6.0a10`. The API under
+!!! info "Opt-in component preview: 2.6.0a11"
+ The first cell installs `fullbleed==2.6.0a11`. The API under
`fullbleed.ui.experimental` remains experimental. Ordinary `pip install
- fullbleed` selects the stable channel. The [current preview guide](../guides/component-preview-10.md)
+ fullbleed` selects the stable channel. The [current preview guide](../guides/component-preview-11.md)
includes the complete source kit, API references and release evidence.
-[{ width="340" }](../assets/styling-notebook-alpha10/field-notes.pdf)
-[{ width="340" }](../assets/styling-notebook-alpha10/edited.pdf)
+[{ width="340" }](../assets/styling-notebook-alpha11/field-notes.pdf)
+[{ width="340" }](../assets/styling-notebook-alpha11/edited.pdf)
-[Open the sample PDF](../assets/styling-notebook-alpha10/field-notes.pdf){ .md-button }
-[Open styled HTML](../assets/styling-notebook-alpha10/field-notes.styled.html){ .md-button }
-[Open unstyled HTML](../assets/styling-notebook-alpha10/field-notes.unstyled.html){ .md-button }
+[Open the sample PDF](../assets/styling-notebook-alpha11/field-notes.pdf){ .md-button }
+[Open styled HTML](../assets/styling-notebook-alpha11/field-notes.styled.html){ .md-button }
+[Open unstyled HTML](../assets/styling-notebook-alpha11/field-notes.unstyled.html){ .md-button }
## Change one control at a time
@@ -88,7 +88,7 @@ For the whole metrics panel, the notebook uses
new component values; they do not mutate other instances. Shared typography
belongs in `Theme` or a reusable `styled(...)` primitive.
-The [authoring reference](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a10/docs/component-bootstrap.md)
+The [authoring reference](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a11/docs/component-bootstrap.md)
explains precedence, supported styles and named parts.
## Run in Colab or local Jupyter
@@ -146,16 +146,17 @@ Bootstrap JavaScript.
The PDF registers bundled Inter explicitly. That registration does not embed
Inter in browser HTML: the host must supply the font or use the included
-sans-serif fallback. The notebook's `@media screen` rules frame the browser
-document and map native `Flow` spacing to browser flex columns. A
-`browser_document` reuses the same component content with those additional
-host rules; the PDF document does not receive them. The
-[complete source kit](../guides/component-preview-10.md) includes an example
+sans-serif fallback. `Flow` now exports its own browser layout and gaps.
+The notebook's `@media screen` rules supply fonts and screen framing. A
+`browser_document` reuses the same component content with those host rules;
+the PDF document does not receive them. PDF-only page breaks are hidden in
+continuous styled HTML. The
+[complete source kit](../guides/component-preview-11.md) includes an example
that writes the font file and a matching browser `@font-face` rule.
## What the checks cover
-The [execution record](../assets/styling-notebook-alpha10/verification.json)
+The [execution record](../assets/styling-notebook-alpha11/verification.json)
covers six cases: default, edited text and styles, selector-only changes,
named-part-only changes, 72 activity rows, and an empty table after the long run.
Independent readers inspect all 23 PDF pages, including text, character bounds,
@@ -163,11 +164,11 @@ colors and finalized page totals. Repeat renders, both HTML forms and ZIP
contents are checked. The default PDF reproduces the qualified field-notes
specimen exactly.
-The [style-isolation record](../assets/styling-notebook-alpha10/style-isolation.json)
+The [style-isolation record](../assets/styling-notebook-alpha11/style-isolation.json)
checks the rendered pixels: changing only `.folio` affects the footer on each
page; changing only the metric value part leaves pages two and three identical.
-The [Jupyter browser record](../assets/styling-notebook-alpha10/browser-verification.json)
+The [Jupyter browser record](../assets/styling-notebook-alpha11/browser-verification.json)
covers editing the controls, running cells, displaying all previews and clicking
the actual ZIP downloads. Those downloads match the independently inspected
files. The checks cover local Jupyter, not execution in a hosted Colab session.
diff --git a/docs/guides/accessible-showcase.md b/docs/guides/accessible-showcase.md
index 465b229..21cc73e 100644
--- a/docs/guides/accessible-showcase.md
+++ b/docs/guides/accessible-showcase.md
@@ -5,8 +5,8 @@ description: A designed two-page Common Ground guide with Python component sourc
# An accessible document, authored with components
-!!! info "Current component preview: 2.6.0a10"
- [Get the current showcase source kit and verified PDFs](component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current showcase source kit and verified PDFs](component-preview-11.md).
The downloadable kits on this page retain their original version pins and evidence.
diff --git a/docs/guides/bootstrap-components-preview.md b/docs/guides/bootstrap-components-preview.md
index ea38206..15f690c 100644
--- a/docs/guides/bootstrap-components-preview.md
+++ b/docs/guides/bootstrap-components-preview.md
@@ -5,8 +5,8 @@ description: Try an experimental Python component kit with Bootstrap 5.0.0 cards
# Bootstrap components, authored in Python
-!!! info "Current component preview: 2.6.0a10"
- [Get the current showcase source kit and verified PDFs](component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current showcase source kit and verified PDFs](component-preview-11.md).
The downloadable kits on this page retain their original version pins and evidence.
diff --git a/docs/guides/component-preview-10.md b/docs/guides/component-preview-10.md
index 18eb32c..3911f54 100644
--- a/docs/guides/component-preview-10.md
+++ b/docs/guides/component-preview-10.md
@@ -5,11 +5,15 @@ description: Compose invoices and reports with reusable Python components, attac
# Build styled documents from Python components
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current component source kit and HTML-export guide](component-preview-11.md).
+ This page retains the alpha 10 package and its verification record.
+
Write a Python function once, then reuse it across invoices, reports and
repeating page headers. Compose semantic building blocks, apply shared styles,
and export PDF, styled HTML or unstyled HTML.
-**2.6.0a10** is the current experimental preview. The source kit includes the
+**2.6.0a10** is a preserved experimental release. The source kit includes the
Northstar invoice, Common Ground reports and an accessible community guide.
Start with the three-page field-notes report below, then edit its functions
and data.
@@ -144,7 +148,7 @@ The [paired-authoring walkthrough](showcase-authoring.md),
examples. The [alpha 9 guide](component-preview-9.md) retains the initial
page-band release, and the [alpha 8 guide](component-preview-8.md) retains the
earlier package without `PageBand` or `PageNumbers`. The
-[styling notebook](../getting-started/component-styling.md) uses alpha 10 and
+[styling notebook](../getting-started/component-styling.md) uses alpha 11 and
lets you edit theme, typography, root, named-part and class-selector styles.
The earlier page-band notebook retains its verified alpha 9 package.
diff --git a/docs/guides/component-preview-11.md b/docs/guides/component-preview-11.md
new file mode 100644
index 0000000..a8ae15f
--- /dev/null
+++ b/docs/guides/component-preview-11.md
@@ -0,0 +1,174 @@
+---
+title: Author styled PDF and HTML with Python components
+description: Compose rich documents with Python functions, reusable styles and Bootstrap helpers. Export PDF, styled HTML or unstyled HTML with the Fullbleed 2.6.0a11 preview.
+---
+
+# One component tree, PDF and HTML
+
+Write a Python function once, then reuse it across invoices, reports and
+repeating page headers. Compose semantic building blocks, attach their styles,
+and export PDF, styled HTML or unstyled HTML.
+
+**2.6.0a11** adds browser layout for `Flow`: its authored gaps work in styled
+HTML without a separate layout rule. PDF output keeps native block pagination.
+The source kit includes the Northstar invoice, Common Ground reports and an
+authored accessible community guide.
+
+[Download the complete source kit](https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.6.0a11/fullbleed-component-showcases-2.6.0a11.zip){ .md-button .md-button--primary }
+[Open the field-notes PDF](https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.6.0a11/common-ground-field-notes-pdfua2-2.6.0a11.pdf){ .md-button }
+[Try the styling notebook](../getting-started/component-styling.md){ .md-button }
+
+
+
+!!! info "Install the preview explicitly"
+ Use Python 3.10 or later in a virtual environment. The kit pins
+ `fullbleed==2.6.0a11`; ordinary `pip install fullbleed` selects the stable
+ channel. Components under `fullbleed.ui.experimental` may change.
+
+## Run the complete report
+
+Extract the ZIP, open its `fullbleed-component-showcases-2.6.0a11` folder, and run:
+
+```sh
+python -m pip install -r requirements.txt
+python component_page_bands/render.py --out output/field-notes --pdf-profile pdfua2
+```
+
+Open `output/field-notes/field-notes.pdf`, `field-notes.html`, or
+`field-notes-unstyled.html`. All content is fictional. Fonts come from the
+pinned package or source kit; PDF generation does not need a browser or
+system fonts.
+
+Edit `component_page_bands/report.py` to change the report's components,
+content and styles. Its `Brand`, `Metric` and `NextStep` functions are reusable
+in other documents.
+
+## Compose and export
+
+This complete example keeps its content and spacing in Python:
+
+```python
+from importlib.resources import files
+from pathlib import Path
+from fullbleed.ui.experimental import (
+ Document, Flow, Heading, Paragraph, Renderer, Theme, component, pt, style,
+)
+
+@component
+def Summary(title, description):
+ return Flow(
+ Heading(title, level=1, style=style(font_size=pt(24))),
+ Paragraph(description),
+ gap=pt(18),
+ )
+
+document = Document(
+ Summary("Project summary", "Prepared by Northstar Studio."),
+ title="Project summary",
+ lang="en-US",
+ theme=Theme(text=style(font_family="Inter, sans-serif", font_size=pt(11))),
+)
+font = files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf")
+renderer = Renderer(font_files=[str(font)])
+Path("summary.pdf").write_bytes(document.render_pdf(renderer))
+Path("summary.html").write_text(document.to_html(include_styles=True), encoding="utf-8")
+Path("summary.unstyled.html").write_text(document.to_html(include_styles=False), encoding="utf-8")
+```
+
+The 18-point gap is part of `Summary`. Styled HTML includes the browser layout
+that applies it; the host does not need to restyle every `Flow`. PDF-only
+`PageBreak` markers are hidden in continuous styled HTML so they do not create
+extra blank gaps.
+
+Unstyled HTML retains semantic structure and class names without generated or
+inline styles. Another application can supply its own design. Both exports are
+static; no Bootstrap JavaScript is included.
+
+## Connect styles to components
+
+`class_name="folio"` adds a class to the exported element. Define the matching
+`.folio` rule in `Document(styles=...)` to give it an appearance. The dot belongs
+in the selector. A Python function name does not create a CSS selector, and a
+class name does not automatically load a stylesheet.
+
+| Purpose | Authoring API |
+| --- | --- |
+| Shared typography and colors | `Theme(...)` on `Document` |
+| Reuse a styled primitive | `styled(...)` |
+| Style one instance | `style=style(...)` |
+| Customize a component's root | `.with_style(...)` |
+| Customize named internal elements | `.with_parts(...)` |
+| Match a class | `Document(styles={".folio": style(...)})` and `class_name="folio"` |
+
+The [editable notebook](../getting-started/component-styling.md) demonstrates
+all five styling paths with independent controls. The
+[authoring reference](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a11/docs/component-bootstrap.md)
+explains precedence, named parts and the static Bootstrap 5.0.0 helpers.
+
+## Add page headers and numbers
+
+The report uses `Page(header=PageBand(...), footer=PageBand(...))` and
+`PageNumbers(total=True, class_name="folio")`. The PDF repeats its bands and
+finalizes values such as `1 / 3`. HTML has one header, main and footer with
+explicit `[page] / [pages]` placeholders.
+
+`Page` reserves the margins; bands do not enlarge them automatically. The
+[page-band reference](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a11/docs/component-page-bands.md)
+covers left, center and right slots, first/blank-page policies, per-record
+numbering and fit diagnostics. Register fonts explicitly so those checks can
+use the actual glyphs.
+
+Browser layout does not preview PDF pagination. Registering a font with
+`Renderer` configures PDF output; browser HTML needs its own font URL or a
+fallback. The source kit writes its font file and a matching `@font-face` rule.
+Its remaining host CSS supplies screen framing.
+
+## Keep both authoring paths
+
+The same kit includes component and HTML/CSS versions of the Northstar invoice
+and Common Ground report, plus the authored accessible guide:
+
+```sh
+python design_showcase/compare_authoring.py --out output/paired
+python accessible_showcase/render.py --out output/accessible-ua1
+python accessible_showcase/render.py --pdf-profile pdfua2 --out output/accessible-ua2
+```
+
+The paired command compares the rendered pages of the two authoring paths.
+The accessible guide demonstrates headings, captioned tables, column headers,
+lists and image descriptions. The
+[paired-authoring walkthrough](showcase-authoring.md),
+[accessible-guide walkthrough](accessible-showcase.md) and
+[Bootstrap catalog](bootstrap-components-preview.md) retain their versioned
+examples.
+
+The [alpha 10 guide](component-preview-10.md) retains the decoration-continuity
+release. The [alpha 9 guide](component-preview-9.md) retains the first
+`PageBand` release and its original notebook.
+
+## Verification and scope
+
+The [release evidence](https://github.com/fullbleed-engine/fullbleed-official/releases/tag/v2.6.0a11)
+records the source, package checksums, saved output and validation scope.
+Local qualification passes 370 component tests and 973 full Python tests.
+The source kit reproduces 11 PDFs using Fullbleed alone, and the styling
+notebook checks six editing scenarios across 23 pages, including actual
+browser ZIP downloads.
+
+The 19 standalone `Flow` HTML fixtures exercise gaps, child margins, nested
+components, fragments, style precedence and page-break markers. Native PDF
+output and the reviewed showcase PDFs retain their previous bytes. Existing
+independent PDF/UA receipts apply only to the identical saved specimens.
+
+The final source-corpus comparison retains **1,623 passes, 21 failures and
+18 disputed references**. All 7,437 saved PDF/PNG files and every fixture verdict
+match alpha 10. The [corpus guide](css-corpus.md) links the complete evidence;
+the failed gate remains visible. This preview does not establish complete CSS
+conformance.
+
+Repeated bands are artifacts outside the PDF body reading order. Keep
+essential identity and report details in the body as well. Profile selection
+and passing specimens do not establish accessibility for arbitrary documents
+or human assistive-technology usability. Fit checks are conservative;
+named/facing-page band variants are outside this API. Native streaming output
+is not atomic.
diff --git a/docs/guides/component-preview-8.md b/docs/guides/component-preview-8.md
index 43d227d..59c73fa 100644
--- a/docs/guides/component-preview-8.md
+++ b/docs/guides/component-preview-8.md
@@ -5,8 +5,8 @@ description: Recreate the Northstar invoice, Common Ground report and accessible
# Python components: make the design your own
-!!! info "Current component preview: 2.6.0a10"
- [Get the current components, page bands and source kit](component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current components, page bands and source kit](component-preview-11.md).
This page retains the alpha 8 package and its verification record.
Start with a complete invoice, report or community guide. Edit Python functions,
diff --git a/docs/guides/component-preview-9.md b/docs/guides/component-preview-9.md
index cd4e84d..9f25237 100644
--- a/docs/guides/component-preview-9.md
+++ b/docs/guides/component-preview-9.md
@@ -5,8 +5,8 @@ description: Build a complete report from Python components, including styled he
# Reuse your design on every page
-!!! info "Current component preview: 2.6.0a10"
- [Get the current components, styling guide and source kit](component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current components, styling guide and source kit](component-preview-11.md).
This page retains the alpha 9 package and its verification record.
A document's header can use the same brand component as its body. Its footer can
diff --git a/docs/guides/css-corpus.md b/docs/guides/css-corpus.md
index 63666c7..0a255c1 100644
--- a/docs/guides/css-corpus.md
+++ b/docs/guides/css-corpus.md
@@ -38,6 +38,25 @@ comparison policy. They do not establish complete CSS support, arbitrary website
rendering, or PDF standards conformance. The historical runs below keep their
original versions and results.
+## Component alpha source comparison: 2.6.0a11
+
+The [alpha 11 source run](https://github.com/fullbleed-engine/fullbleed-official/actions/runs/39403828268)
+qualifies the source tree for commit `c9958ccb19d10f0ecf11cbc3500f2151844394b9` against all 1,662
+fixtures. It records **1,623 PASS, 21 FAIL and 18 REFERENCE-DISPUTED**. The
+comparison gate still **fails**.
+
+All **7,437 saved PDF/PNG files** and every fixture verdict match the retained
+alpha 10 source result below. The HTML-export spacing change does not alter
+these native PDF outputs. No comparator inputs, references, rasterizer or
+thresholds were changed.
+
+[Download the complete alpha 11 source evidence](https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.6.0a11/fullbleed-css-corpus-2.6.0a11.zip)
+or read the [component and HTML-export guide](component-preview-11.md).
+This is a source-built Linux wheel comparison. The release's separate public
+installation checks do not constitute a complete corpus run against a
+downloaded alpha wheel. The interactive explorer remains pinned to the
+published stable wheel above.
+
## Component alpha source comparison: 2.6.0a10
The [alpha 10 source run](https://github.com/fullbleed-engine/fullbleed-official/actions/runs/39367401326)
diff --git a/docs/guides/pandas-components.md b/docs/guides/pandas-components.md
index 1e76881..33c90f3 100644
--- a/docs/guides/pandas-components.md
+++ b/docs/guides/pandas-components.md
@@ -205,6 +205,6 @@ The PDF uses the tagged profile and passes source structural checks. These check
do not establish PDF/UA conformance or assistive-technology usability. For that
separate workflow, see the [accessible component showcase](accessible-showcase.md).
-Continue with the [current component preview](component-preview-10.md),
+Continue with the [current component preview](component-preview-11.md),
[reusable page-band notebook](../getting-started/page-band-notebook.md), or
[HTML/CSS pandas report](pandas-to-pdf.md).
diff --git a/docs/guides/python-components.md b/docs/guides/python-components.md
index 15e4662..556a174 100644
--- a/docs/guides/python-components.md
+++ b/docs/guides/python-components.md
@@ -16,7 +16,7 @@ no browser, virtual DOM, hook lifecycle, or JavaScript runtime involved.
The complete workflow below uses Fullbleed **2.5.25**.
!!! tip "Help shape the next authoring model"
- The [component alpha](component-preview-10.md) adds components
+ The [component alpha](component-preview-11.md) adds components
that carry their styles, named customization parts, column-based tables,
static Bootstrap helpers, and styled or unstyled HTML export. Install it
explicitly to try the next API, or [edit the styling notebook](../getting-started/component-styling.md)
diff --git a/docs/guides/showcase-authoring.md b/docs/guides/showcase-authoring.md
index f6ac909..b33224b 100644
--- a/docs/guides/showcase-authoring.md
+++ b/docs/guides/showcase-authoring.md
@@ -5,8 +5,8 @@ description: Build the Northstar invoice and Common Ground report with HTML/CSS
# One design, two authoring paths
-!!! info "Current component preview: 2.6.0a10"
- [Get the current showcase source kit and verified PDFs](component-preview-10.md).
+!!! info "Current component preview: 2.6.0a11"
+ [Get the current showcase source kit and verified PDFs](component-preview-11.md).
The downloadable kits on this page retain their original version pins and evidence.
diff --git a/docs/index.md b/docs/index.md
index 06a920e..f111152 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -73,7 +73,7 @@ Using C#? [Compose PDFs with typed methods, LINQ and style objects](guides/cshar
or [download the component invoice](assets/dotnet-components/project.zip).
Want to try the next Python authoring API? The
-[component alpha](guides/component-preview-10.md) includes reusable styles,
+[component alpha](guides/component-preview-11.md) includes reusable styles,
named component parts, data tables, static Bootstrap helpers and repeating page
bands, with PDF and styled or unstyled HTML output. [Explore the versioned component
notebook](getting-started/component-notebook.md) in Colab or Jupyter, or download
diff --git a/examples/styling-notebook-alpha11/component_styling.ipynb b/examples/styling-notebook-alpha11/component_styling.ipynb
new file mode 100644
index 0000000..2fa91f3
--- /dev/null
+++ b/examples/styling-notebook-alpha11/component_styling.ipynb
@@ -0,0 +1,479 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "intro",
+ "metadata": {},
+ "source": [
+ "# Make the components your own\n",
+ "\n",
+ "Change five styling controls in a complete Common Ground report, then compare\n",
+ "the PDF, styled HTML and unstyled HTML. The document uses Python functions and\n",
+ "component values; its content does not need authored HTML strings.\n",
+ "\n",
+ "| Control | What it demonstrates |\n",
+ "| --- | --- |\n",
+ "| `ACCENT` | A shared color in `Theme`, read with `token(\"accent\")` |\n",
+ "| `TITLE_SIZE` | A reusable `styled(Heading, ...)` primitive |\n",
+ "| `PANEL_BACKGROUND` | `.with_style(...)` on the metrics panel's root |\n",
+ "| `METRIC_COLOR` | `.with_parts(value=...)` on each `Metric` |\n",
+ "| `FOLIO_COLOR` | A `.folio` rule matched by `class_name=\"folio\"` |\n",
+ "\n",
+ "Run all cells, edit one control in step 2, and rerun from there through the\n",
+ "download cell. Change one value at a time to see which elements it affects.\n",
+ "The PDF includes repeating headers and finalized page totals.\n",
+ "\n",
+ "This notebook explicitly installs **Fullbleed 2.6.0a11**. Its component API is\n",
+ "experimental. All content is fictional. Fullbleed and this notebook are MIT\n",
+ "licensed. Use a standard CPU runtime in Colab or local Jupyter and download\n",
+ "your work before leaving the hosted session."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "install-help",
+ "metadata": {},
+ "source": [
+ "## 1. Install the pinned preview\n",
+ "\n",
+ "Fullbleed generates the PDF without a browser or system fonts. The notebook host supplies its display and download interface."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "install-fullbleed",
+ "metadata": {},
+ "source": [
+ "%pip install --quiet --only-binary=:all: \"fullbleed==2.6.0a11\""
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "controls-help",
+ "metadata": {},
+ "source": [
+ "## 2. Make it yours\n",
+ "\n",
+ "Start with the name, headline, accent color, and activity rows. All text values\n",
+ "remain text, including `<` and `&`. After an edit, rerun this cell and every cell\n",
+ "below it. Add activity rows to see the page total change after pagination."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "report-controls",
+ "metadata": {},
+ "source": [
+ "PROJECT_NAME = \"Common Ground\"\n",
+ "HEADLINE = \"A season of shared space.\"\n",
+ "# Five independent styling paths; try changing one at a time.\n",
+ "ACCENT = \"#755122\" # Theme token: the small uppercase labels.\n",
+ "TITLE_SIZE = 31 # styled(): all large report headings, in points.\n",
+ "PANEL_BACKGROUND = \"#edf2e6\" # with_style(): the first-page metrics panel.\n",
+ "METRIC_COLOR = \"#183e34\" # with_parts(): only the large metric values.\n",
+ "FOLIO_COLOR = \"#53665c\" # class_name + selector: the page numbers.\n",
+ "ACTIVITY = [\n",
+ " (\"Garden mornings\", \"12\", \"84 visits\"),\n",
+ " (\"Repair afternoons\", \"4\", \"31 items repaired\"),\n",
+ " (\"Shared meals\", \"3\", \"96 places set\"),\n",
+ " (\"Seed exchanges\", \"2\", \"47 packets shared\"),\n",
+ " (\"Welcome walks\", \"6\", \"28 neighbors\"),\n",
+ "]"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "styles-help",
+ "metadata": {},
+ "source": [
+ "## 3. Give the components a shared style\n",
+ "\n",
+ "`Theme` supplies inherited text and named colors. `styled(...)` creates reusable\n",
+ "defaults. `class_name=\"folio\"` below adds a literal class; the `.folio` selector\n",
+ "in `Document(styles=...)` defines how the page counter looks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "shared-styles",
+ "metadata": {},
+ "source": [
+ "# SPDX-License-Identifier: MIT\n",
+ "\"\"\"A fictional seasonal brief built from reusable component values.\"\"\"\n",
+ "from fullbleed.ui.experimental import (\n",
+ " Box, Column, DataTable, Document, Flow, Heading, Page, PageBand, PageBreak,\n",
+ " PageNumbers, Paragraph, Row, Stack, Strong, Theme, component, mm, pt, style,\n",
+ " styled, token,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "THEME = Theme(\n",
+ " {\"ink\": \"#183e34\", \"muted\": \"#53665c\", \"paper\": \"#edf2e6\", \"accent\": ACCENT},\n",
+ " text=style(font_family=\"Inter\", font_size=pt(10), line_height=1.45, color=token(\"ink\")),\n",
+ ")\n",
+ "Kicker = styled(Paragraph, font_size=pt(7), font_weight=700, letter_spacing=pt(1),\n",
+ " text_transform=\"uppercase\", color=token(\"accent\"))\n",
+ "Title = styled(Heading, font_size=pt(TITLE_SIZE), line_height=1.08, letter_spacing=pt(-0.8))\n",
+ "Small = styled(Paragraph, font_size=pt(8), color=token(\"muted\"))"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "components-help",
+ "metadata": {},
+ "source": [
+ "## 4. Compose reusable pieces\n",
+ "\n",
+ "`Brand`, `Metric`, and `NextStep` return ordinary component trees. Named `part`\n",
+ "values let a caller use `.with_parts(...)` to customize an internal element.\n",
+ "The same `Brand()` value can be used in a page band or in document content."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "reusable-components",
+ "metadata": {},
+ "source": [
+ "@component(parts=(\"name\", \"detail\"))\n",
+ "def Brand():\n",
+ " return Stack(\n",
+ " Paragraph(PROJECT_NAME.upper(), part=\"name\", style=style(font_size=pt(8), font_weight=700, letter_spacing=pt(0.8))),\n",
+ " Paragraph(\"A neighborhood, growing together.\", part=\"detail\", style=style(font_size=pt(6), color=token(\"muted\"))),\n",
+ " gap=pt(2),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@component(parts=(\"value\", \"label\"))\n",
+ "def Metric(value, label):\n",
+ " return Stack(\n",
+ " Paragraph(value, part=\"value\", style=style(font_size=pt(32), font_weight=700, line_height=1.1)),\n",
+ " Small(label, part=\"label\"), gap=pt(5),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@component(parts=(\"heading\", \"body\"))\n",
+ "def NextStep(number, title, text):\n",
+ " return Stack(\n",
+ " Kicker(number),\n",
+ " Heading(title, level=3, part=\"heading\", style=style(font_size=pt(16))),\n",
+ " Paragraph(text, part=\"body\"), gap=pt(6),\n",
+ " )"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "document-help",
+ "metadata": {},
+ "source": [
+ "## 5. Put the report together\n",
+ "\n",
+ "`PageBand` places components in left, center, or right margin slots.\n",
+ "`PageNumbers(total=True)` uses final page counts. Page margins must leave enough\n",
+ "space for the bands; a fit error identifies the page, slot, and authored source.\n",
+ "\n",
+ "The bands repeat as PDF artifacts outside body reading order. Essential identity\n",
+ "and report information also belong in the body.\n",
+ "\n",
+ "The short lesson box uses `break_inside=\"avoid\"` to keep its heading and body\n",
+ "together when a longer activity table pushes it to the next page."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "style-connections",
+ "metadata": {},
+ "source": [
+ "### Follow a style to its target\n",
+ "\n",
+ "`class_name=\"folio\"` on `PageNumbers` emits the class. The matching\n",
+ "`Document(styles={\".folio\": style(...)})` supplies its appearance. The dot\n",
+ "belongs in the selector; omit it from `class_name`. A class name does not\n",
+ "automatically load Bootstrap or any other stylesheet.\n",
+ "\n",
+ "`Metric` explicitly declares the `value` and `label` parts. Its\n",
+ "`.with_parts(value=style(...))` call changes the value without changing the\n",
+ "label. `.with_style(...)` changes a component's root. These calls return new\n",
+ "values, so one instance can be customized without changing its siblings.\n",
+ "\n",
+ "Try `METRIC_COLOR = \"#8c2f6b\"` first. Then restore it and try\n",
+ "`FOLIO_COLOR = \"#206b7a\"`. The named part affects the first page's two numbers;\n",
+ "the selector affects the footer on every page.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "compose-document",
+ "metadata": {},
+ "source": [
+ "def build_document():\n",
+ " activity = ACTIVITY\n",
+ " return Document(\n",
+ " Flow(\n",
+ " Kicker(\"Field notes / Autumn 2026\"),\n",
+ " Title(HEADLINE, level=1),\n",
+ " Paragraph(\"A small record of what happens when neighbors have a place to meet, make, and lend a hand.\"),\n",
+ " Box(\n",
+ " Row(\n",
+ " Metric(\"126\", \"neighbors took part\").with_parts(value=style(color=METRIC_COLOR)),\n",
+ " Metric(\"21\", \"open sessions\").with_parts(value=style(color=METRIC_COLOR)),\n",
+ " gap=pt(18),\n",
+ " ),\n",
+ " style=style(background=token(\"paper\"), padding=pt(18)),\n",
+ " ).with_style(background=PANEL_BACKGROUND),\n",
+ " Heading(\"The place is the invitation\", level=2, style=style(font_size=pt(17))),\n",
+ " Paragraph(\"We kept the gate open, the table long, and the activities simple. New faces became familiar ones. Practical work made room for conversation.\"),\n",
+ " Small(f\"{PROJECT_NAME} is a fictional community project. Every figure in this brief is sample data.\"),\n",
+ " gap=pt(15),\n",
+ " ),\n",
+ " PageBreak(),\n",
+ " Flow(\n",
+ " Kicker(\"01 / What we made possible\"),\n",
+ " Title(\"Small actions. Shared results.\", level=2),\n",
+ " Paragraph(\"The busiest sessions mixed a useful task with time to stay. No specialist knowledge was needed to join in.\"),\n",
+ " DataTable(\n",
+ " activity, caption=\"Season activity\", columns=(\n",
+ " Column(\"Activity\", lambda row: row[0]),\n",
+ " Column(\"Sessions\", lambda row: row[1], align=\"right\", width=mm(18)),\n",
+ " Column(\"Participation\", lambda row: row[2], width=mm(37)),\n",
+ " ),\n",
+ " ).with_parts(\n",
+ " caption=style(font_size=pt(9), font_weight=700, margin_bottom=pt(7)),\n",
+ " header=style(background=token(\"paper\"), font_size=pt(8), color=token(\"ink\")),\n",
+ " cell=style(font_size=pt(8), padding=pt(8)),\n",
+ " ),\n",
+ " Box(\n",
+ " Kicker(\"A useful lesson\"),\n",
+ " Paragraph(Strong(\"Make the first step visible. \"), \"A welcome at the gate, a clearly marked tool bench, and a task that takes ten minutes helped people begin.\"),\n",
+ " style=style(background=token(\"paper\"), padding=pt(15), break_inside=\"avoid\"),\n",
+ " ),\n",
+ " Small(\"Visits and participation counts can include the same neighbor more than once. They describe activity, not unique attendance.\"),\n",
+ " gap=pt(12),\n",
+ " ),\n",
+ " PageBreak(),\n",
+ " Flow(\n",
+ " Kicker(\"02 / What comes next\"),\n",
+ " Title(\"Keep the welcome growing.\", level=2),\n",
+ " Paragraph(\"Three modest commitments will guide the next season. Each has a clear owner and a way for someone new to help.\"),\n",
+ " NextStep(\"01\", \"Make room at the table\", \"Reserve a few places at every shared meal for neighbors joining for the first time. Publish the menu and access information together.\"),\n",
+ " NextStep(\"02\", \"Pass on a practical skill\", \"Pair each repair session with a short demonstration. Keep a simple printed guide beside the tools for people who prefer to follow at their own pace.\"),\n",
+ " NextStep(\"03\", \"Listen before adding more\", \"Ask what made participation easy and what got in the way. Use those answers to choose the next opening hours and activities.\"),\n",
+ " Small(f\"Prepared by {PROJECT_NAME} / October 2026. This sample is authored with Fullbleed components, including its repeated header and finalized page numbers.\"),\n",
+ " gap=pt(15),\n",
+ " ),\n",
+ " title=f\"{PROJECT_NAME} — Field notes\", lang=\"en-US\", theme=THEME,\n",
+ " page=Page(\n",
+ " \"A5\", margin=(mm(20), mm(17), mm(20), mm(17)),\n",
+ " header=PageBand(left=Brand(), right=Kicker(\"Field notes / 03\").with_style(font_size=pt(6))),\n",
+ " footer=PageBand(\n",
+ " left=Small(\"AUTUMN 2026 · SAMPLE DATA\").with_style(font_size=pt(6)),\n",
+ " right=PageNumbers(total=True, prefix=\"\", separator=\" / \", class_name=\"folio\"),\n",
+ " ),\n",
+ " ),\n",
+ " styles={\".folio\": style(font_size=pt(8), color=FOLIO_COLOR)},\n",
+ " )\n",
+ "\n",
+ "\n",
+ "document = build_document()"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "render-help",
+ "metadata": {},
+ "source": [
+ "## 6. Render PDF and export HTML\n",
+ "\n",
+ "The bundled Inter font is registered explicitly for PDF generation. Styled HTML\n",
+ "keeps its font-family declaration; a browser host supplies Inter or a fallback.\n",
+ "Unstyled HTML keeps content and classes while removing generated and inline styles.\n",
+ "Both exports contain one header/main/footer sequence and explicit `[page]` /\n",
+ "`[pages]` placeholders. They do not add JavaScript or browser pagination.\n",
+ "\n",
+ "The PDF/UA-2 profile and source checks express authoring intent. Review and\n",
+ "validate your own output; this notebook does not establish arbitrary-document\n",
+ "accessibility or replace human assistive-technology testing."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "render-export",
+ "metadata": {},
+ "source": [
+ "from importlib import metadata, resources\n",
+ "from pathlib import Path\n",
+ "from fullbleed.ui.experimental import Renderer\n",
+ "\n",
+ "assert metadata.version(\"fullbleed\") == \"2.6.0a11\"\n",
+ "output = Path(\"output\")\n",
+ "output.mkdir(exist_ok=True)\n",
+ "font = resources.files(\"fullbleed_assets\").joinpath(\"fonts/Inter-Variable.ttf\")\n",
+ "renderer = Renderer(\n",
+ " font_files=[str(font)], pdf_profile=\"pdfua2\",\n",
+ " document_timestamp=\"2026-10-19T00:00:00Z\",\n",
+ ")\n",
+ "prepared = document.prepare(renderer, a11y_mode=\"raise\")\n",
+ "pdf_path = output / \"field-notes.pdf\"\n",
+ "pdf_path.write_bytes(prepared.render_pdf())\n",
+ "# Reuse the same content with browser-only presentation rules.\n",
+ "# Flow already exports its browser layout and gaps. Keep font fallbacks\n",
+ "# and screen framing out of the PDF document.\n",
+ "browser_document = Document(\n",
+ " *document.children,\n",
+ " title=document.title, lang=document.lang, page=document.page, theme=document.theme,\n",
+ " styles=document.styles + \"\"\"\n",
+ "@media screen {\n",
+ " body, [data-fb-band-slot] { font-family: Inter, sans-serif; }\n",
+ " body { max-width: 680px; margin: 0 auto; padding: 24px; }\n",
+ " main > [data-fb-flow] { margin-bottom: 40px; }\n",
+ " [data-fb-page-band] { margin: 12px 0 28px; }\n",
+ " footer[data-fb-page-band] { margin-top: 28px; }\n",
+ "}\n",
+ "\"\"\",\n",
+ ")\n",
+ "for include_styles, filename in (\n",
+ " (True, \"field-notes.styled.html\"),\n",
+ " (False, \"field-notes.unstyled.html\"),\n",
+ "):\n",
+ " export_document = browser_document if include_styles else document\n",
+ " (output / filename).write_text(\n",
+ " export_document.to_html(include_styles=include_styles, a11y_mode=\"raise\"),\n",
+ " encoding=\"utf-8\", newline=\"\\n\",\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\"field-notes-page-{number}.png\"\n",
+ " path.write_bytes(png)\n",
+ " preview_paths.append(path)\n",
+ " display(Image(data=png, width=540))\n",
+ "print(f\"{len(preview_paths)} pages. Review every page before using the document.\")"
+ ],
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "download-help",
+ "metadata": {},
+ "source": [
+ "## 7. Download the current result\n",
+ "\n",
+ "The ZIP contains the PDF, both HTML exports, and this run's page previews. In\n",
+ "Colab the next cell starts a download; local Jupyter shows a download link.\n",
+ "The individual files also remain in `output/`. Shorter reruns exclude old,\n",
+ "longer-run previews from the ZIP."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "download",
+ "metadata": {},
+ "source": [
+ "import sys\n",
+ "from zipfile import ZipFile, ZIP_DEFLATED\n",
+ "\n",
+ "bundle_path = output / \"component-field-notes.zip\"\n",
+ "current_files = [pdf_path, output / \"field-notes.styled.html\",\n",
+ " output / \"field-notes.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",
+ "- [Page-band guide and complete source kit](https://docs.fullbleed.dev/guides/component-preview-9/)\n",
+ "- [API, fit diagnostics, and limitations](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a11/docs/component-page-bands.md)\n",
+ "- [Northstar and Common Ground through both authoring paths](https://docs.fullbleed.dev/guides/showcase-authoring/)\n",
+ "- [Share a small authoring example](https://github.com/fullbleed-engine/fullbleed-official/discussions/86)\n",
+ "\n",
+ "Native streaming output is not atomic. Managed fit checks are conservative.\n",
+ "Named or facing-page band variants are not part of this preview. Bootstrap\n",
+ "components and HTML exports remain static."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "export-styling-scope",
+ "metadata": {},
+ "source": [
+ "## Use the HTML in another application\n",
+ "\n",
+ "Both HTML files contain the same authored content. Styled HTML includes the\n",
+ "component and document CSS. Unstyled HTML retains semantic structure and\n",
+ "classes, including `folio`, but contains no style declarations; your host can\n",
+ "provide its own CSS. Both exports are static, without Bootstrap JavaScript.\n",
+ "\n",
+ "The PDF uses explicitly registered, bundled Inter. Registering that font with\n",
+ "`Renderer` does not embed it in browser HTML; the browser host must provide\n",
+ "Inter or use a fallback. HTML has one header/main/footer sequence and\n",
+ "`[page] / [pages]` placeholders. It does not paginate like the PDF.\n",
+ "The included `@media screen` stylesheet supplies a sans-serif fallback and\n",
+ "screen framing. `Flow` exports its own browser layout and gaps; no host\n",
+ "layout rule is needed. PDF-only page breaks are hidden in styled HTML.\n",
+ "The host stylesheet belongs to `browser_document`, which shares the same\n",
+ "component content. The PDF document does not receive those host rules.\n",
+ "\n",
+ "For a longer report, put 72 rows in `ACTIVITY` and rerun. Use `ACTIVITY = []`\n",
+ "afterwards to check the empty state. The ZIP includes only the current PDF,\n",
+ "HTML files and previews, even after a longer run.\n",
+ "\n",
+ "The PDF/UA-2 profile and source checks express authoring intent. Validate your\n",
+ "own output and perform human assistive-technology review when needed. Repeated\n",
+ "PDF bands sit outside body reading order; essential information also belongs\n",
+ "in the body.\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ },
+ "colab": {
+ "name": "component_page_bands.ipynb",
+ "provenance": []
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/mkdocs.yml b/mkdocs.yml
index 5f4bc51..5696113 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -128,7 +128,7 @@ nav:
- Engine architecture: engine/overview.md
- Evaluate:
- Component design preview: guides/component-design-preview.md
- - Current component preview: guides/component-preview-10.md
+ - Current component preview: guides/component-preview-11.md
- Bootstrap component alpha: guides/bootstrap-components-preview.md
- Compare Python PDF libraries: guides/comparison.md
- Three-renderer comparison: guides/renderer-comparison.md
diff --git a/tools/build_styling_notebook.py b/tools/build_styling_notebook.py
index 7bd7159..2a52745 100644
--- a/tools/build_styling_notebook.py
+++ b/tools/build_styling_notebook.py
@@ -1,4 +1,5 @@
"""Build the focused styling tutorial from the preserved field-notes notebook."""
+from argparse import ArgumentParser
from copy import deepcopy
import json
from pathlib import Path
@@ -11,7 +12,8 @@
)
-def build():
+def build(channel="alpha10"):
+ assert channel in ("alpha10", "alpha11")
notebook = deepcopy(json.loads(SOURCE.read_text(encoding="utf-8")))
cells = {cell["id"]: cell for cell in notebook["cells"]}
@@ -160,6 +162,35 @@ def replace(name, before, after):
in the body.
""".splitlines(keepends=True),
})
+ if channel == "alpha11":
+ for cell in notebook["cells"]:
+ cell["source"] = "".join(cell["source"]).replace(
+ "2.6.0a10", "2.6.0a11").splitlines(keepends=True)
+ replace(
+ "render-export",
+ "# Native Flow spacing needs flex-column layout in a browser. Keep those\n"
+ "# host rules out of the PDF document; no HTML string rewriting is needed.",
+ "# Flow already exports its browser layout and gaps. Keep font fallbacks\n"
+ "# and screen framing out of the PDF document.",
+ )
+ replace(
+ "render-export",
+ " [data-fb-flow] { display: flex; flex-direction: column; }\n",
+ "",
+ )
+ scope = next(cell for cell in notebook["cells"] if cell["id"] == "export-styling-scope")
+ old = ("The included `@media screen` stylesheet frames the browser document, provides\n"
+ "a sans-serif fallback, and maps native `Flow` spacing to browser flex columns.\n"
+ "It belongs to `browser_document`, which shares the same component content.\n"
+ "The PDF document does not receive these host rules.")
+ new = ("The included `@media screen` stylesheet supplies a sans-serif fallback and\n"
+ "screen framing. `Flow` exports its own browser layout and gaps; no host\n"
+ "layout rule is needed. PDF-only page breaks are hidden in styled HTML.\n"
+ "The host stylesheet belongs to `browser_document`, which shares the same\n"
+ "component content. The PDF document does not receive those host rules.")
+ text = "".join(scope["source"])
+ assert text.count(old) == 1
+ scope["source"] = text.replace(old, new).splitlines(keepends=True)
for cell in notebook["cells"]:
if cell["cell_type"] == "code":
assert cell["outputs"] == [] and cell["execution_count"] is None
@@ -167,8 +198,12 @@ def replace(name, before, after):
if __name__ == "__main__":
- data = build()
- for destination in DESTINATIONS:
+ parser = ArgumentParser(description=__doc__)
+ parser.add_argument("--channel", choices=("alpha10", "alpha11"), default="alpha10")
+ args = parser.parse_args()
+ data = build(args.channel)
+ for original in DESTINATIONS:
+ destination = Path(str(original).replace("styling-notebook-alpha10", "styling-notebook-" + args.channel))
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(data)
print(f"Built the styling notebook and identical download ({len(data)} bytes).")
diff --git a/tools/check_component_styling_ui.py b/tools/check_component_styling_ui.py
index 5cbf813..4da6e92 100644
--- a/tools/check_component_styling_ui.py
+++ b/tools/check_component_styling_ui.py
@@ -17,14 +17,22 @@ def main():
parser.add_argument("--base-url")
parser.add_argument("--browser", choices=("chrome", "chromium"), default="chrome")
parser.add_argument("--verify-published-source", action="store_true")
+ parser.add_argument("--allow-local-candidate", action="store_true",
+ help="Local review only: permit evidence generated from an unpublished wheel.")
args = parser.parse_args()
root = args.site.resolve()
out = args.out.resolve()
out.mkdir(parents=True, exist_ok=False)
- asset_path = "/assets/styling-notebook-alpha10/"
+ asset_path = "/assets/styling-notebook-alpha11/"
assets = root / asset_path.lstrip("/")
manifest = json.loads((assets / "verification.json").read_text(encoding="utf-8"))
assert manifest["ok"] and not manifest["visual_review_pending"]
+ assert manifest["runtime_version"] == "2.6.0a11"
+ browser_evidence = json.loads((assets / "browser-verification.json").read_text(encoding="utf-8"))
+ assert browser_evidence["ok"] and browser_evidence["notebook_sha256"] == manifest["notebook_sha256"]
+ if not args.allow_local_candidate:
+ assert manifest["public_install_cell"] and not manifest["local_install_override"], "Public package execution is required."
+ assert not browser_evidence["local_install_override"], "Public package browser execution is required."
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *a, **kw):
@@ -56,7 +64,9 @@ def log_message(self, *a):
article = page.locator("article")
assert article.get_by_role("heading", name="Make the components your own", exact=False).is_visible()
text = article.inner_text()
- for marker in ("2.6.0a10", "class_name", "with_parts", "with_style", "to_html",
+ if not args.allow_local_candidate:
+ assert "Unpublished candidate" not in text
+ for marker in ("2.6.0a11", "class_name", "with_parts", "with_style", "to_html",
"ACCENT", "TITLE_SIZE", "PANEL_BACKGROUND", "METRIC_COLOR", "FOLIO_COLOR",
"23 PDF pages", "local Jupyter", "component-field-notes.zip"):
assert marker in text, marker
@@ -123,14 +133,25 @@ def log_message(self, *a):
animations="disabled", full_page=True)
for path, title in (
("/", "Try the current styling notebook"),
- ("/guides/component-preview-10/", "Try the styling notebook"),
+ ("/guides/component-preview-11/", "Try the styling notebook"),
("/guides/python-components/", "edit the styling notebook"),
):
response = page.goto(base + path, wait_until="networkidle")
assert response.status == 200
+ if not args.allow_local_candidate:
+ assert "Unpublished candidate" not in page.locator("article").inner_text()
link = page.locator("article").get_by_role("link", name=title, exact=True)
assert link.count() == 1
assert urlparse(link.evaluate("a => a.href")).path == "/getting-started/component-styling/"
+ assert page.evaluate("document.documentElement.scrollWidth <= innerWidth + 1")
+ if path == "/guides/component-preview-11/":
+ assert page.locator("article").get_by_role(
+ "heading", name="One component tree, PDF and HTML", exact=False
+ ).is_visible()
+ assert page.locator("article img").evaluate_all(
+ "images => images.length > 0 && images.every(img => img.complete && img.naturalWidth > 0)"
+ )
+ page.screenshot(path=str(out / f"{label}-preview-guide.png"), animations="disabled")
assert not errors, errors
results.append(dict(viewport=label, width=width, colab_url=colab, source_url=source,
actual_notebook_download=True, notebook_sha256=manifest["notebook_sha256"],
@@ -147,6 +168,7 @@ def log_message(self, *a):
server.server_close()
result = dict(ok=True, base_url=base, browser=browser_version, cases=results,
pinned_public_source_verified=args.verify_published_source,
+ local_candidate_allowed=args.allow_local_candidate,
visual_review_pending=True,
scope="Three landing-page widths, actual notebook downloads, exact PDF/HTML/evidence assets, CSS selector behavior in static HTML and three discovery links. Hosted Colab execution is not covered.")
(out / "verification.json").write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
diff --git a/tools/verify_page_band_notebook.py b/tools/verify_page_band_notebook.py
index 49221c3..cd7b74d 100644
--- a/tools/verify_page_band_notebook.py
+++ b/tools/verify_page_band_notebook.py
@@ -160,10 +160,10 @@ def main():
parser = ArgumentParser(description=__doc__)
parser.add_argument('--notebook', type=Path, required=True)
parser.add_argument('--out', type=Path, required=True)
- parser.add_argument('--channel', choices=('alpha9', 'alpha10'), default='alpha9')
+ parser.add_argument('--channel', choices=('alpha9', 'alpha10', 'alpha11'), default='alpha9')
parser.add_argument('--local-wheel', type=Path, help='Local qualification only: substitute this verified wheel for the public install cell.')
args = parser.parse_args()
- version = '2.6.0a10' if args.channel == 'alpha10' else '2.6.0a9'
+ version = {'alpha9': '2.6.0a9', 'alpha10': '2.6.0a10', 'alpha11': '2.6.0a11'}[args.channel]
out = args.out.resolve()
out.mkdir(parents=True, exist_ok=False)
source_path = args.notebook.resolve()
@@ -176,7 +176,7 @@ def main():
edited = deepcopy(default)
edited.update(PROJECT_NAME='River ', HEADLINE='A place to meet & make.', ACCENT='#763c96')
edited['ACTIVITY'][0] = ('Studio & cards', '16', '89 visits')
- if args.channel == 'alpha10':
+ if args.channel in ('alpha10', 'alpha11'):
assert {'PANEL_BACKGROUND', 'METRIC_COLOR', 'FOLIO_COLOR', 'TITLE_SIZE'} <= default.keys()
edited.update(PANEL_BACKGROUND='#f1e8f5', METRIC_COLOR='#8c2f6b',
FOLIO_COLOR='#206b7a', TITLE_SIZE=27)
@@ -185,7 +185,7 @@ def main():
empty = deepcopy(default)
empty['ACTIVITY'] = []
cases = [('default', default), ('edited', edited)]
- if args.channel == 'alpha10':
+ if args.channel in ('alpha10', 'alpha11'):
selector = deepcopy(default)
selector['FOLIO_COLOR'] = '#206b7a'
parts = deepcopy(default)
@@ -216,7 +216,7 @@ def main():
reports.append(check(work, destination, name, controls, version))
print(json.dumps(dict(case=name, pages=reports[-1]['pages'], ok=True)), flush=True)
assert len({row['pdf_sha256'] for row in reports}) == len(cases)
- if args.channel == 'alpha10':
+ if args.channel in ('alpha10', 'alpha11'):
from PIL import Image, ImageChops
isolation = {}
for name in ('selector-only', 'parts-only'):