diff --git a/.claude/commands/review-notebook.md b/.claude/commands/review-notebook.md new file mode 100644 index 0000000..d587e00 --- /dev/null +++ b/.claude/commands/review-notebook.md @@ -0,0 +1,13 @@ +--- +description: LLM review of a master notebook (content, cross-cell, staging, tags) +argument-hint: +--- + +Read `docs/Notebook_Review_Prompt_V1-00.md` (repo root: the Palladium repository +this command lives in) and apply it, in full, to the notebook at: $ARGUMENTS + +Follow the prompt exactly: read the pattern docs it names, read the notebook's raw +`.ipynb` JSON (cell sources, tags, and outputs — not a rendered view), and open the +master's engine package for cross-checks where the prompt directs. Produce ONLY the +findings report in the prompt's output format — no preamble, no summary of what you +did. diff --git a/.gitignore b/.gitignore index 55b56d4..1a0399d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,26 @@ # Palladium-specific .env +.DS_Store + +# Generated exports — never committed (masters and assessments) studies/*/exports/* !studies/*/exports/.gitkeep +assessments/*/exports/* +!assessments/*/exports/.gitkeep + +# Client / engagement documents must NEVER be committed — masters stay +# client-clean (see CLAUDE.md, Confidentiality). Text/image source material +# is allowed; binary documents are blocked by default. ONE exception: a +# Study's published, public source PDF — add with `git add -f` and say so +# in the commit message. +studies/*/docs/* +!studies/*/docs/*.md +!studies/*/docs/*.png +assessments/*/docs/* +!assessments/*/docs/*.md +!assessments/*/docs/*.png +*SOW* # IPython profile_default/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f6a09ba --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,261 @@ +# CLAUDE.md — Palladium (library of master notebooks) + +🐾 Palladium is a **library of master notebooks** for consulting delivery: Mercury-served +Jupyter deliverables a client watches on a live call. "Library" means the copies in this +repo are **masters** — client-clean, reusable, maintained — it does **not** mean drafts. +A master becomes a client deliverable only as an **engagement copy made outside this +repo**, where it acquires client data and becomes confidential. The notebook a client +can open and trust is the whole product. + +## 🐾 Red Panda Approval™ — for a consulting master library + +Don't satisfy a checklist — satisfy the red pandas. Ask of each change: *does this +earn approval?* + +1. **Rebuildable From Nothing** — inside any master: `python -m venv .venv` → + `pip install -e ".[dev]"` → `pytest` → headless gate → `mercury --working-dir .` + brings up the deliverable with no manual step. And a master **copied out of the + repo runs standalone** — self-contained engine, own `staging.py`, no import back + into Palladium. The ability to stand a workshop up on short notice is the product. +2. **Elegant Simplicity** — these are workshop tools, not enterprise platforms; the + obvious solution, done well. A content cell should read like the survey it came + from, not like a framework. +3. **Observable & Debuggable** — a failing gate names the number that drifted; + diagnostics flow through `backstage()`, never onto the client-facing stage; the + exports show the whole session state so a failure is diagnosable after the call. +4. **Consistent Patterns** — the three-layer contract, widget-pairs, tagged cells, + per-master engine packages. Match the pattern docs, not personal taste. +5. **Actually Works** — "pytest was green" is not "the board renders on the stage + with the engine's numbers." The gate under headless `nbconvert --execute` plus an + actual Mercury render are the proof. + +Criteria 1 and 5 are **externally verifiable** — the rebuild works or it doesn't; the +stage renders or it doesn't. Verify them, don't assert them. Criteria 2–4 are +judgement calls: when in doubt, match what the repo already does rather than grading +your own elegance. + +> If a paw print isn't leading the response, the rest of this file probably isn't +> being honoured either. Lead with one. 🐾 + +--- + +## The three-layer contract + +Every master separates three concerns; the split IS the architecture: + +| Layer | Audience | Holds | Never holds | +|---|---|---|---| +| **Mercury** (the stage) | the client | the polished board/case, sidebar controls | diagnostics, question scripts, internals | +| **Notebook cells** (backstage) | the consultant | **content** (topic banks, prompts) and **client data** (engagement facts, spend, headcount) in **tagged cells**; presentation code | math | +| **Python modules** (the engine) | tests + the notebook | reusable logic, schema, calculations — typed, `mypy --strict`, pinned | **content — NEVER** | + +The rule that has teeth: **content never lives in `.py`** — not as constants, not as a +notebook generator's string blocks. The notebook is the document the consultant edits; +hiding its content in importable modules defeats the point of a notebook. Mechanics: +[docs/Mercury_Notebook_Pattern_V1-00.md](docs/Mercury_Notebook_Pattern_V1-00.md); +per-type contracts: [docs/Assessment_Pattern_V1-00.md](docs/Assessment_Pattern_V1-00.md) +and [docs/Study_Pattern_V1-00.md](docs/Study_Pattern_V1-00.md). + +## Taxonomy + +- **Study** (`studies/YYYYMM_…`, dated) — reproduction of a dated base document + (Forrester TEI or similar); verbatim anchor + client overlay; the date is the + publication's, so it stays. +- **Assessment** (`assessments/Instrument_Name`, undated) — reusable workshop + instrument; a living master. Reference implementation: + `assessments/CX_Discovery_Workshop/`. +- **Engagement copy** (`YYYYMM_Client_Instrument`, stamped at copy-time) — a master + copied OUT of this repo for a client; confidential; never merges back. +- **`template/MercuryNotebook/`** — copy-me scaffold for new studies (py-engine model + only — see Known liabilities). +- **`core/`** — the shared Athena toolkit; masters do NOT import it. + +## Before working in a master: check where you are + +**Look at the path.** Which master's root are you under? Each master has its **own +venv** — `.venv` inside the master directory, provisioned by `pip install -e ".[dev]"` +there; the repo-root `.venv` (from `make setup`) serves only `core/` and the notebook +structural suite. Running a master's pytest from the wrong venv is the classic +"missing module" ghost. + +- `MERCURY_CONFIG_DIR` present in the environment = **the stage is live** (a client + may be looking). Its presence is the stage signal `staging.py` keys off — never set + it manually except to simulate the stage in a test. +- Only masters live here. **If a notebook in this repo contains a real client's name, + something is wrong** — stop and flag it (see Confidentiality). + +## Risk tier: CONSULTING MASTERS — what you may run + +**Free** — run without asking: +`pytest`, `mypy`, `ruff`, `make test`, `make check-notebooks`, +`jupyter nbconvert --execute` on a master, `mercury --working-dir .` locally, +`python scripts/export_report.py`, and any read-only git. + +**Show first** — produce the diff/output, present it, wait for a human "go": +content edits to `topic-bank`/`engagement-data` cells or anchor-adjacent wording; +re-pinning a gate or test after a content/engine change (show the pin diff and the +KPI moves honestly); moving or renaming a master; `.gitignore` changes; `git commit`. + +**Forbidden without explicit go-ahead:** +`git push`; editing `*_VERBATIM` anchors; committing any client document (SOW, quote, +NDA'd vendor deck); putting a client's name or data into a master; history rewrites; +deleting a study or assessment. + +> The one clause that always applies: *"the user asked me to update the workshop" is +> not explicit go-ahead for a Show-first change.* Explicit go-ahead is the user seeing +> the specific diff and saying yes to **that**. + +--- + +## Conventions (always-on) + +### Done means the stage shows the engine's numbers, not that the file saved + +The cell is a wish; the executed notebook is the fact. An edit that is syntactically +perfect has changed **nothing** until the notebook re-runs — and the committed outputs +now lie about the deliverable. + +1. **Pin it** — `pytest` + `mypy` in the master's venv. +2. **Execute it** — `jupyter nbconvert --to notebook --execute --inplace + notebooks/*.ipynb`; the gate passing headless is the study's smoke test. +3. **Export it** — `python scripts/export_report.py`; the `.md` must carry the + appendix and its final JSON block. +4. **Read it back from the real system** — serve with Mercury (or open the exported + HTML) and look at the board; then check what depends on the change: test pins, + gate pins, README counts, pattern docs, the export JSON. A master is a *chain* — + a renamed content key breaks widgets, notes, and the export two hops downstream. + +### Confidentiality — masters stay client-clean + +There is no Palladium without this rule; it is what makes the library shareable. + +- Masters carry **placeholder** engagement data, published or synthetic numbers, and + no client statements. Client personalization happens in an **engagement copy** made + outside the repo (copy → rename `YYYYMM_Client_…` → fresh venv → fill + `engagement-data` → verify; canonical checklist in the Assessment Pattern) — + **before** any client data is entered, and the copy never merges back. +- **Never commit a client document** — SOWs, quotes, pricing decks, NDA'd vendor + material. `.gitignore` blocks binary documents under every master's `docs/` and + anything matching `*SOW*`; that is a guardrail, not permission — the rule is the + rule even where the pattern has a hole. The ONE exception: a Study's **published, + public** source PDF, added deliberately with `git add -f` and named in the commit + message. +- Improvements discovered on an engagement come back to the master as **clean edits** + (content/logic only, client facts stripped). + +### Notebooks + +- Widget-pair rule, gate cell, stage/backstage, data appendix, packaging: the + [Mercury pattern](docs/Mercury_Notebook_Pattern_V1-00.md) is the contract — read it + before editing any notebook. +- Tagged cells are the consultant's surface: `topic-bank` (content), + `engagement-data` (client facts; placeholders in masters), `presentation`, `gate`, + `data-appendix`. Tags are load-bearing — tests, the structural suite, and the + export pipeline all find cells by tag. +- Content-cell keys are **stable identities** (widgets, notes, exports key off them); + a title edit renames its sidebar widget label, which resets that widget's state + mid-session. +- `assessments/CX_AI_Diagnostic/notebooks/diagnostic.ipynb` is **generated** by its + `scripts/build_notebook.py` — never hand-edit it (and don't "fix" it to + notebook-first casually; that's a recorded redesign). + +### Python + +- One venv per master; `pip install -e ".[dev]"` provisions everything (whole + toolchain as core deps — never a `requirements.txt` in a master). +- Engines are `mypy --strict` clean; new logic lands as engine code + pins **before** + the notebook section that renders it. +- `staging.py` is copied per master (self-containment beats DRY here — an engagement + copy must run alone). The mypy-strict variant in + `assessments/CX_Discovery_Workshop/discoverylib/staging.py` (with `backstage_md`) + is the canonical form for new masters. + +## Always-on anti-patterns + +- **Content:** never move workshop content into `.py` — including generators that + hold cell sources as strings. +- **Client data:** never in a master; never in a commit; a client name in + `assessments/` or `template/` means the copy-out step was skipped. +- **Anchors:** never edit `*_VERBATIM` — overlay corrections, don't rewrite the + record. +- **Widgets:** never read `.value` in the defining cell; widget cells emit no output. +- **Surfaces:** never build a parallel UI (Streamlit twin, second app) — the notebook + is the surface. +- **Exports:** never commit generated exports; `exports/` is gitignored on purpose. +- **Emptiness pins:** never assert that engagement placeholders are empty — the + filled engagement copy must stay green. + +--- + +## Environment + +- Repo root: `/home/robert/notebook/git/palladium` · remote: + `ssh://git@git.helu.ca:22022/r/palladium.git` (Robert's Gitea) · branch `main`. +- Root venv: `make setup` (core/Athena layer + the notebook structural suite; + `make test`, `make check-notebooks`). One venv **per master** besides it. +- Python ≥3.10 per master (`CX_AI_Diagnostic` pins ≥3.11; root core is ≥3.11); + Mercury 3.2.x; per-master mypy is strict; repo-wide lint is ruff (root + `pyproject.toml`). +- Athena onboarding: `00_setup.ipynb` (writes `.env`; sandbox + `https://athena.ouranos.helu.ca`). + +## Known liabilities (flag, don't silently fix) + +If you find a known non-compliant choice, raise it rather than quietly fixing it or +quietly leaving it. Live ones worth knowing: + +- **`studies/202607_CTM_GenesysCX/` is a real-client engagement study inside the + library** — committed confidential vendor/client PDFs in `docs/` (also in git + history), plus a signed SOW PDF sitting on disk untracked-and-ignored (the `*SOW*` + ignore hides it from `git status` — it is still there). Grandfathered: do not add + more material, do not use it as precedent; extraction to an engagement archive is + a recorded follow-up. Two of its notebooks are dirty in the working tree — + pre-existing, leave them. +- **`assessments/CX_AI_Diagnostic/` pre-dates the notebook-first model** — generated + notebook, content in YAML configs. Redesign pending; until then its shape is + intentional. Its `mypy` is also not clean in a fresh venv (11 pre-existing errors: + missing pandas/PyYAML/plotly stubs, unannotated functions) even though its + `pyproject.toml` declares strict mode — pytest and the headless execute are green; + fix the typing with the redesign, not piecemeal. +- **TEI twins + CTM pre-date the tagged-cell taxonomy** — the structural suite + grandfathers them by name in `tests/nbcheck.py`, each with its reason. +- **`template/MercuryNotebook/` encodes only the py-engine model** (and its + `staging.py` lacks the mypy-strict `backstage_md` variant); rework is a recorded + follow-up. Its `exports/*.{html,md}` are tracked — predates the exports rule. +- **`docs/brand.md` references a `brand_dark.md` that does not exist.** +- **`core.bootstrap.init(study=…)` imports `studies..config`** — vestigial + (no study ships a `config.py`; `studies/__init__.py` exists to serve it). Don't + remove the `__init__.py` without retiring that path. + +## Identified opportunities (recorded, not built) + +- A private installable Palladium package on the Gitea (CI exists there) — would + replace per-master `staging.py` copies; revisit when the duplication bites. +- A copy-out helper script (`scripts/new_engagement.py`) — after the manual checklist + has been exercised a few times. +- CTM extraction to a confidential engagement archive. +- TEI + AI Diagnostic redesigns to the notebook-first model (each moves its notebook + from GRANDFATHERED to NOTEBOOK_FIRST in `tests/nbcheck.py`). +- Template rework: an Assessment template derived from the Discovery reference; + staging.py mypy-strict retrofit across masters. + +## Reference + +**Read the pattern for your master type before working in it — they hold the detail +this file summarises:** + +- [docs/Assessment_Pattern_V1-00.md](docs/Assessment_Pattern_V1-00.md) — assessments: + notebook-first content, engagement-data cell, copy-out checklist +- [docs/Study_Pattern_V1-00.md](docs/Study_Pattern_V1-00.md) — studies: verbatim + anchor + overlay, reproduction gate, published-PDF exception + +Everything else: + +- [docs/Mercury_Notebook_Pattern_V1-00.md](docs/Mercury_Notebook_Pattern_V1-00.md) — + the shared mechanics (reactivity, gate, staging, appendix, packaging) +- [docs/Notebook_Review_Prompt_V1-00.md](docs/Notebook_Review_Prompt_V1-00.md) — the + LLM review; run as `/review-notebook ` +- [docs/brand.md](docs/brand.md) — NTT DATA palette for stages and exports +- [docs/Athena_TEI.md](docs/Athena_TEI.md) + `docs/Athena API.yaml` — the core/Athena + layer +- [README.md](README.md) — taxonomy, quick start, engagement workflow diff --git a/Makefile b/Makefile index a2cdd8d..ebfdf81 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VENV := .venv PY := $(VENV)/bin/python PIP := $(VENV)/bin/pip -.PHONY: setup lab test lint format clean +.PHONY: setup lab test check-notebooks lint format clean ## One-time: create venv, install deps + palladium (editable) setup: @@ -19,10 +19,16 @@ setup: lab: $(VENV)/bin/jupyter lab -## Run the test suite (no Athena connection needed — HTTP is mocked) +## Run the test suite (no Athena connection needed — HTTP is mocked). +## Includes the notebook structural suite (tests/test_notebooks.py). test: $(PY) -m pytest tests/ -v +## Structural checks for every master notebook (kernel-free; -rs shows +## why grandfathered notebooks skip the notebook-first tier) +check-notebooks: + $(PY) -m pytest tests/test_notebooks.py -rs + lint: $(VENV)/bin/ruff check . diff --git a/README.md b/README.md index 84cb9a5..6769648 100644 --- a/README.md +++ b/README.md @@ -1,425 +1,139 @@ # Palladium -**TEI (Total Economic Impact) Calculator** — The strategic artifact that protects the business case. +**A library of master notebooks for consulting delivery** — workshop instruments and +business-case studies, built as Mercury-served Jupyter deliverables. -Palladium is a Jupyter-notebook toolkit for building Total Economic Impact analyses. Each study is a self-contained Mercury-served notebook deliverable (math in a study package, verification gate, LLM-readable exports); a small shared `core/` talks to [Athena](https://athena.nttdata.com) for client/opportunity context and server-side TEI tooling. +> *In Greek mythology, the Palladium was a sacred artifact of Athena that protected +> Troy. Whoever possessed it held strategic advantage. In our ecosystem, Palladium +> protects the deal — transforming discovery inputs into a case no CFO can ignore.* -> *In Greek mythology, the Palladium was a sacred artifact of Athena that protected Troy. Whoever possessed it held strategic advantage. In our ecosystem, Palladium protects the deal — transforming discovery inputs into a financial case no CFO can ignore.* +Every master obeys one **three-layer contract** (the always-on rules live in +[`CLAUDE.md`](CLAUDE.md)): -## Architecture +- **Mercury** is the polished, client-facing interface — the screen you share in a + workshop, remote or in person. +- **Jupyter notebook cells** are the consultant's interface — workshop **content** + and **client data** are edited in tagged cells, never in `.py` files. +- **Python modules** hold reusable logic and calculations worth calling as a module, + typed and covered by validation tests. -``` -┌──────────────────────────────────────────────────────────────────┐ -│ Palladium │ -│ │ -│ studies/YYYYMM_TEI_Vendor_Product/ ← self-contained study │ -│ studies/YYYYMM_Client_EngagementName/ (own venv + engine) │ -│ ├─ / ← ALL math, verbatim anchors │ -│ ├─ notebooks/ ← THE deliverable (Mercury-served) │ -│ ├─ tests/ ← pinned acceptance numbers │ -│ └─ exports/ ← .html/.md + JSON appendix (for LLMs, │ -│ and the Athena repository roadmap) │ -│ │ -│ core/ ← shared Athena toolkit (studies do NOT import it) │ -│ tei_client → ──────────────────────────► Athena API │ -│ calculations · export · cli · bootstrap │ -└──────────────────────────────────────────────────────────────────┘ -``` +## Taxonomy -### Components +| Kind | What it is | Naming | Lives | +|---|---|---|---| +| **Study** | Reproduction of a dated base document (Forrester TEI or similar), personalized as an overlay | `YYYYMM_TEI_Vendor_Product` / `YYYYMM_Client_Engagement` (dated) | `studies/` | +| **Assessment** | Reusable workshop instrument (discovery workshop, diagnostic) | `Instrument_Name` (undated, living) | `assessments/` | +| **Engagement copy** | A master copied out for a client engagement — acquires client data, becomes **confidential** | `YYYYMM_Client_Instrument`, stamped at copy-time | **outside this repo** | -| Component | Purpose | -|-----------|---------| -| **`studies/`** | One self-contained folder per engagement — own venv, engine package, Mercury notebook, tests, exports | -| **`template/`** | Copy-me study scaffold — start here for new studies | -| **`core/tei_client`** | Python API client for Athena's TEI endpoints | -| **`core/calculations`** | Financial logic — NPV, ROI, payback, risk adjustment, scenarios | -| **`core/export`** | Builds the structured JSON envelope consumed by the report pipeline | -| **`core/cli`** | `python -m palladium` command-line interface | +Masters in this repo stay **client-clean**: placeholder engagement data, published or +synthetic numbers, nothing a client said. Patterns: +[Assessment](docs/Assessment_Pattern_V1-00.md) · +[Study](docs/Study_Pattern_V1-00.md) · +[shared Mercury mechanics](docs/Mercury_Notebook_Pattern_V1-00.md). -> **All studies follow the [Mercury Notebook Deliverable Pattern](docs/Mercury_Notebook_Pattern_V1-00.md)**: -> the notebook *is* the artifact — self-contained study package, Mercury-served, -> gate-verified, LLM-exportable. Start from [`template/MercuryNotebook/`](template/MercuryNotebook/). -> The Streamlit `app/` and `core/notebook_helpers` were retired when the last -> legacy study migrated (git history keeps them). - ---- - -## Quick Start — Jupyter Lab first - -Palladium is a **Jupyter Lab-first** environment. Everything starts from a -notebook; the CLI is a companion, not a prerequisite. - -```bash -git clone https://github.com/nttdata/palladium.git -cd palladium -make setup # venv + deps + editable install (one time) -make lab # launches Jupyter Lab -``` - -Then open **`00_setup.ipynb`** at the repo root. It will: - -1. Prompt for your Athena API key (hidden input) and save it to `.env` -2. Test the connection -3. Show what report templates and tools exist in the instance - -Current target instance: **https://athena.ouranos.helu.ca** (Ouranos sandbox — -no production data, safe to experiment). - -From any root-level notebook, the Athena connection is one import (pattern -studies are self-contained and never import `core`): - -```python -from core.bootstrap import init - -pal = init() # loads .env, builds client, tests it -pal.client.list_reports() -``` - -### Configuration - -All credentials and IDs live in `/.env` (gitignored). `00_setup.ipynb` -writes it for you; to do it by hand: - -```bash -# .env -ATHENA_BASE_URL=https://athena.ouranos.helu.ca -ATHENA_API_KEY=your-api-key-here -``` - -### Verify Connection - -In a notebook: `init()` prints the connection status. From a shell: - -```bash -python -m palladium test -``` - ---- - -## Usage - -### Run a study end-to-end - -Each new study is self-contained under the -[Mercury Notebook Deliverable Pattern](docs/Mercury_Notebook_Pattern_V1-00.md). -The reference TEI study is the February 2026 Forrester *Total Economic -Impact™ Of Amazon Connect* (pattern Variant 4 — composite reproduction): - -```bash -cd studies/202602_TEI_Amazon_Connect -python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]" -mercury --working-dir notebooks/ # serve the deliverable (the stage) -python scripts/export_report.py # export .html/.md report sources -``` - -Its notebook reproduces the published totals within the PDF's rounding — -**NPV $78.7M • ROI 342% • Payback <6 months** — and the verification gate -asserts it on every headless run. See the study's README for details. - -`studies/202512_TEI_Genesys_CX_Cloud/` follows the same shape (**NPV $10.8M -• ROI 266%**), with one signature input: the Genesys AI Experience token -line the published study models at $0, priced live from the client's quote. -`studies/202607_CTM_GenesysCX/` is the full multi-notebook reference -implementation. - -### CLI - -```bash -# Test connection -python -m palladium test - -# List TEI tool instances -python -m palladium list - -# List available report templates -python -m palladium reports - -# Show financial summary for a tool -python -m palladium summary - -# Trigger server-side recalculation -python -m palladium calculate - -# Export for the report pipeline -python -m palladium export -o export.json -``` - -### Tests - -```bash -pytest tests/ -v -``` - -The root suite covers the API client (mocked HTTP), the financial math, and -the export envelope shape; the Amazon Connect verbatim anchor is asserted -against the published Forrester totals. Each study additionally carries its -own pinned suite (`cd studies/ && pytest`). - ---- - -## Adding a new study - -Copy the template, not an existing study: - -```bash -cp -r template/MercuryNotebook studies/YYYYMM_TEI_Vendor_Product -``` - -Then follow `template/MercuryNotebook/README.md`: rename `studylib/` to -your study package (underscores only — dashes break Python imports), -replace the toy model, re-pin the tests, rework the notebook. -`studies/202602_TEI_Amazon_Connect/` is the worked TEI example; -`studies/202607_CTM_GenesysCX/` is the full multi-notebook reference. - ---- - -## TEI Methodology - -Palladium implements the Forrester TEI™ framework. - -### Benefit Categories - -Benefits are quantified across categories, risk-adjusted, and discounted to present value: - -| Category | Examples | -|----------|----------| -| **Cost Savings** | Legacy license elimination, reduced headcount, lower telecom | -| **Productivity** | Reduced handle time, faster training, automated QA | -| **Revenue** | Improved retention, better conversion, new channel revenue | -| **Risk Reduction** | Compliance automation, reduced legal exposure, audit readiness | - -### Risk Adjustment - -Each benefit carries a risk-adjustment factor (0–50%) reflecting implementation uncertainty. -A 20% risk adjustment on a $10M benefit yields a risk-adjusted value of $8M. -**Costs** are risk-adjusted **upward** by the same factor (higher risk → higher modelled cost). - -### Financial Metrics - -| Metric | Description | -|--------|-------------| -| **NPV** | Net Present Value — total risk-adjusted benefits minus costs, discounted | -| **ROI** | Return on Investment — `(benefits − costs) / costs × 100` | -| **Payback** | Months until cumulative benefits exceed cumulative costs | - -The initial investment (year 0) is **not** discounted. Year-N cashflows are -discounted at the end of the year: `PV = CF_n / (1 + r)^n`. This matches -the Forrester methodology used in the published studies. - -### Scenario Analysis - -Three scenarios model uncertainty in adoption and realization -(see `core.calculations.SCENARIOS`): - -| Scenario | Adoption | Risk delta | Effect | -|----------|----------|------------|--------| -| Conservative | 80% | +10pp on benefits | Lower benefits, higher modelled cost | -| Moderate | 100% | 0 | Base case (= published study) | -| Aggressive | 115% | –5pp on benefits | Higher benefits, lower padding on cost | - ---- - -## Project Structure +## Repository layout ``` palladium/ -├── 00_setup.ipynb # ← START HERE: credentials + connection -├── Makefile # make setup / lab / test -├── core/ # Shared, study-agnostic Python package -│ ├── bootstrap.py # one-import notebook setup (init, save_credentials) -│ ├── tei_client/ # Athena API client -│ │ ├── client.py # TEIClient with all /api/v1/tei/ methods -│ │ └── models.py # Optional dataclasses for typed access -│ ├── calculations/ # Pure-python financial math -│ │ ├── npv.py -│ │ ├── roi.py -│ │ ├── payback.py -│ │ └── scenarios.py -│ ├── export/ -│ │ └── report_data.py # JSON envelope for the report pipeline -│ └── cli/ -│ └── main.py # `python -m palladium ...` -├── palladium/ # CLI shim (just exposes `python -m palladium`) -│ └── __main__.py -├── template/ -│ └── MercuryNotebook/ # copy-me pattern scaffold (runnable) -├── studies/ # One self-contained folder per engagement -│ ├── 202512_TEI_Genesys_CX_Cloud/ # CX Cloud TEI — pattern Variant 4 -│ │ ├── README.md # NPV $10.8M · ROI 266% + the $0 AI-token line -│ │ ├── teicalc/ # self-contained engine (anchor/model/overlay) -│ │ ├── notebooks/business_case.ipynb -│ │ ├── tests/ · scripts/ · config.toml · pyproject.toml -│ │ └── docs/ # Forrester PDF + Genesys token-metering notes -│ ├── 202602_TEI_Amazon_Connect/ # Amazon Connect TEI — pattern Variant 4 -│ │ ├── README.md # NPV $78.7M · ROI 342%, reproduced + gated -│ │ ├── teicalc/ # self-contained engine (anchor/model/overlay) -│ │ ├── notebooks/business_case.ipynb -│ │ ├── tests/ · scripts/ · config.toml · pyproject.toml -│ │ ├── exports/ # generated; .gitignored -│ │ └── docs/ -│ │ └── 202602_TEI Report Amazon Connect.pdf -│ └── 202607_CTM_GenesysCX/ # CTM × Genesys study — pattern reference impl -├── tests/ # root tests for core/ -│ ├── test_client.py -│ ├── test_calculations.py -│ └── test_export.py -├── Athena API.yaml # OpenAPI reference -├── .env.example -├── requirements.txt -├── pyproject.toml -└── README.md +├── CLAUDE.md # the always-on contract (rubric, layers, risk tiers) +├── assessments/ +│ ├── CX_Discovery_Workshop/ # ★ reference implementation (notebook-first model) +│ └── CX_AI_Diagnostic/ # capability diagnostic (pre-redesign: generated notebook) +├── studies/ +│ ├── 202512_TEI_Genesys_CX_Cloud/ # Forrester TEI reproduction — NPV $10.8M · ROI 266% +│ ├── 202602_TEI_Amazon_Connect/ # Forrester TEI reproduction — NPV $78.7M · ROI 342% +│ └── 202607_CTM_GenesysCX/ # client engagement study (grandfathered — see CLAUDE.md) +├── template/MercuryNotebook/ # copy-me scaffold (py-engine model; see "Adding a master") +├── docs/ # the pattern docs, brand.md, review prompt, Athena reference +├── core/ # shared Athena toolkit (masters do NOT import it) +├── tests/ # core tests + the notebook structural suite (all masters) +├── 00_setup.ipynb # Athena credentials + connection (core layer) +└── Makefile # make setup / lab / test / check-notebooks ``` ---- +Each master is **self-contained**: its own engine package, venv, tests, Mercury +config, and export script — a copy of the directory runs standalone. -## Athena Integration - -Palladium connects to Athena's TEI module for data persistence and cross-tool reporting. - -### API Endpoints Used - -All endpoints are under `/api/v1/tei/` and require `Authorization: Api-Key {key}`. - -| Endpoint | Purpose | -|----------|---------| -| `GET /api/v1/tei/reports/` | List available TEI report templates | -| `GET /api/v1/tei/reports/{public_id}/` | Get a report template | -| `GET /api/v1/tei/reports/{public_id}/fields/` | Get field definitions for a template | -| `POST /api/v1/tei/tools/` | Create a new TEI tool instance | -| `GET /api/v1/tei/tools/{public_id}/` | Get instance metadata | -| `PATCH /api/v1/tei/tools/{public_id}/` | Update name/status | -| `GET /api/v1/tei/tools/{public_id}/values/` | Get current field values | -| `PUT /api/v1/tei/tools/{public_id}/values/` | Bulk-update values | -| `PATCH /api/v1/tei/tools/{public_id}/values/{field_key}/` | Patch a single value | -| `POST /api/v1/tei/tools/{public_id}/calculate/` | Trigger calculation | -| `GET /api/v1/tei/tools/{public_id}/summary/` | Get financial summary | -| `GET /api/v1/tei/tools/{public_id}/versions/` | List version snapshots | -| `POST /api/v1/tei/tools/{public_id}/versions/` | Save a new version | -| `GET /api/v1/tei/tools/{public_id}/versions/{n}/` | Get a specific version | -| `GET /api/v1/tei/tools/{public_id}/export/` | Export for the report pipeline | -| `GET /api/v1/tei/summary/` | Aggregate NPV across all tools | - -### Object model - -| Athena object | Notes | -|---|---| -| **Opportunity** | Top-level sales record. Owns one or more **Proposals**. | -| **Proposal** | A specific bid/offer to a client. **A TEI tool is linked to a Proposal.** | -| **Engagement** | Optional — for active client engagements. A TEI tool may also link here. | -| **TEIReport** | Template (e.g. *Amazon Connect 2026*) — defines fields, discount rate, analysis horizon. | -| **TEITool** | Instance of a Report bound to a Proposal — holds values, summaries, versions. | - -### Authentication - -``` -Authorization: Api-Key {your-api-key} -``` - -API keys are provisioned in Athena's admin interface per user/service account. - -### Methodology conventions (Palladium ↔ Athena) - -Two places where the Forrester methodology and the Athena TEI API differ, and -how Palladium bridges them: - -| Topic | Athena behaviour | Palladium convention | -|---|---|---| -| **Cost risk adjustment** | Costs are never risk-adjusted server-side | Cost values are pushed pre-multiplied by `(1 + risk_adj)`; field-level adjustment stays 0 | -| **Year-0 "Initial" costs** | No year-0 concept; non-annual values are folded into Year 1 | Each cost gets a companion non-annual `_initial` field. `TEIClient` folds them back into an `initial` key on read. Athena discounts these as Year 1 (Forrester doesn't discount Year 0) — expect ≈0.15% drift on cost PV | - ---- - -## Report Pipeline Integration - -Palladium's export produces structured JSON consumed by the LLM report generation pipeline: - -``` -Palladium Export (JSON) - │ - ▼ -Peitho — LLM generates HTML (following HTML_DOCUMENT_FORMAT.md) - │ - ▼ -html2docx converts to native Word - │ - ▼ -Professional TEI Report (.docx) -``` - -The export envelope (`core.export.build_report_data`) includes: -- All benefit categories with risk-adjusted values -- All cost categories with yearly breakdown (and Initial column) -- Financial summary (NPV, ROI, payback, yearly cashflow) -- Conservative / moderate / aggressive scenario analysis -- Metadata (study slug, proposal, engagement, generator stamp) -- The raw Athena `/export/` payload for reference - ---- - -## Version Management - -Athena keeps version history for TEI tools, driven through the API -(`core.tei_client`: `save_version` / `list_versions` / `get_version`): - -1. **Save Version** — Snapshots current values + summary with a descriptive note -2. **View History** — All versions with headline metrics (NPV, ROI) -3. **Compare Versions** — Side-by-side diff of value changes between any two versions -4. **Restore Version** — Load a previous version's values as the current state - -Version notes should capture: -- Assumptions made and their sources -- Which scenario the version represents -- What changed since the previous version -- Client confirmations or corrections - ---- - -## Development - -### Running Tests +## Quick start — run a master ```bash -pytest tests/ -v +git clone ssh://git@git.helu.ca:22022/r/palladium.git && cd palladium + +cd assessments/CX_Discovery_Workshop +python -m venv .venv && source .venv/bin/activate +pip install -e ".[dev]" + +mercury --working-dir . # the stage — share this screen with the client +jupyter lab # backstage — edit content/data cells, see the script +pytest # content + engagement + engine pins +python scripts/export_report.py # exports/*.html + LLM-ready *.md ``` -Tests are designed to run without an Athena connection — HTTP is mocked -and the calculation suite uses the Amazon Connect seed data to verify the -Forrester numbers reproduce within rounding. +Every master's README carries its own specifics; the commands are the same shape in +all of them. -### Code Style +## Client engagements — copy out, never in place + +A master never touches client data. To run one for a client (full checklist in the +[Assessment Pattern](docs/Assessment_Pattern_V1-00.md)): + +1. **Copy** the master directory out of Palladium to your engagement location. +2. **Rename** it `YYYYMM_Client_Instrument` (e.g. `202608_Acme_CX_Discovery`). +3. **Provision** a fresh venv there (`pip install -e ".[dev]"` — copied venvs are broken). +4. **Fill the `engagement-data` cell** with the client facts. +5. **Verify** — `pytest` + the headless gate stay green by design. + +The copy is now **confidential**: it lives with the engagement, never merges back. +Improvements found on engagements are hand-carried to the master as clean edits. +`.gitignore` blocks client documents repo-wide — see `CLAUDE.md` § Confidentiality. + +## Validation + +Two mechanisms guard every master: + +**Deterministic** — per master (in its venv): engine pins, content pins read from the +notebook's tagged cells, the stage/backstage test, and the in-notebook verification +gate under headless `nbconvert --execute`. Repo-wide (root venv): +`make check-notebooks` runs the kernel-free structural suite over every committed +notebook — parses, cleanly executed, tag taxonomy present ([tests/nbcheck.py](tests/nbcheck.py) +classifies every notebook; grandfathered ones skip with a recorded reason). + +**LLM review** — [`docs/Notebook_Review_Prompt_V1-00.md`](docs/Notebook_Review_Prompt_V1-00.md) +hunts what deterministic checks can't: content oddities, cross-cell rot, stale +numbers, stage leakage. Run it in-repo with `/review-notebook ` (Claude Code), +or paste the prompt into any LLM alongside the `.ipynb` JSON. + +## Adding a new master + +For an **Assessment**, start from the reference implementation and its pattern doc — +`assessments/CX_Discovery_Workshop/` + [Assessment Pattern](docs/Assessment_Pattern_V1-00.md). +For a **Study**, copy `template/MercuryNotebook/` and follow the +[Study Pattern](docs/Study_Pattern_V1-00.md) (note: the template still encodes the +py-engine model; its notebook-first rework is a recorded follow-up). Either way: +underscores in names (never dashes — directories are Python packages), and register +the new notebook in [tests/nbcheck.py](tests/nbcheck.py) — the completeness test +fails until you classify it. + +## Athena / core (the TEI toolkit) + +The `core/` package talks to [Athena](https://athena.nttdata.com) for +client/opportunity context and server-side TEI tooling — masters do **not** import +it. Start at **`00_setup.ipynb`** (`make setup && make lab`): it prompts for the API +key, writes `.env`, and tests the connection (current target: +`https://athena.ouranos.helu.ca`, the Ouranos sandbox). ```bash -ruff check . -ruff format . +python -m palladium test | list | reports | summary | calculate | export -o export.json ``` -### Adding a New Benefit Category +The export JSON feeds the report pipeline: **Palladium → Peitho (LLM → HTML) → +html2docx → .docx**. Full API reference, object model, calculation logic, and the +Palladium↔Athena methodology bridges: [`docs/Athena_TEI.md`](docs/Athena_TEI.md) + +[`docs/Athena API.yaml`](docs/Athena%20API.yaml). Root tests (`make test`) cover the +client (mocked HTTP), the financial math, the export envelope — and the notebook +structural suite. -1. Define the field in Athena's TEI Report admin (field name, type, category, defaults) -2. The field automatically appears in Palladium via the API — no client changes -3. Update notebook prose if category-specific commentary is needed -4. If the report template exposes a new structure, extend the envelope in - `core/export/report_data.py` - ---- - -## Dependencies - -| Package | Version | Purpose | -|---------|---------|---------| -| `requests` | ≥2.31 | HTTP client for Athena API | -| `python-dotenv` | ≥1.0 | Environment configuration | -| `jupyter` | ≥1.0 | Notebook environment | -| `pandas` | ≥2.0 | Data manipulation | -| `plotly` | ≥5.18 | Interactive visualizations | -| `numpy` | ≥1.26 | Financial calculations | -| `pytest` | ≥7.4 | Testing | -| `ruff` | ≥0.1 | Linting and formatting | - ---- - -## Related Projects +## Related projects | Project | Relationship | |---------|-------------| diff --git a/studies/202607_CX_AI_Diagnostic/README.md b/assessments/CX_AI_Diagnostic/README.md similarity index 100% rename from studies/202607_CX_AI_Diagnostic/README.md rename to assessments/CX_AI_Diagnostic/README.md diff --git a/studies/202607_CX_AI_Diagnostic/config.toml b/assessments/CX_AI_Diagnostic/config.toml similarity index 100% rename from studies/202607_CX_AI_Diagnostic/config.toml rename to assessments/CX_AI_Diagnostic/config.toml diff --git a/studies/202607_CX_AI_Diagnostic/configs/base.yaml b/assessments/CX_AI_Diagnostic/configs/base.yaml similarity index 100% rename from studies/202607_CX_AI_Diagnostic/configs/base.yaml rename to assessments/CX_AI_Diagnostic/configs/base.yaml diff --git a/studies/202607_CX_AI_Diagnostic/configs/contact_center.yaml b/assessments/CX_AI_Diagnostic/configs/contact_center.yaml similarity index 100% rename from studies/202607_CX_AI_Diagnostic/configs/contact_center.yaml rename to assessments/CX_AI_Diagnostic/configs/contact_center.yaml diff --git a/studies/202607_CX_AI_Diagnostic/configs/financial_services.yaml b/assessments/CX_AI_Diagnostic/configs/financial_services.yaml similarity index 100% rename from studies/202607_CX_AI_Diagnostic/configs/financial_services.yaml rename to assessments/CX_AI_Diagnostic/configs/financial_services.yaml diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/__init__.py b/assessments/CX_AI_Diagnostic/diaglib/__init__.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/__init__.py rename to assessments/CX_AI_Diagnostic/diaglib/__init__.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/config.py b/assessments/CX_AI_Diagnostic/diaglib/config.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/config.py rename to assessments/CX_AI_Diagnostic/diaglib/config.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/export.py b/assessments/CX_AI_Diagnostic/diaglib/export.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/export.py rename to assessments/CX_AI_Diagnostic/diaglib/export.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/models.py b/assessments/CX_AI_Diagnostic/diaglib/models.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/models.py rename to assessments/CX_AI_Diagnostic/diaglib/models.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/scoring.py b/assessments/CX_AI_Diagnostic/diaglib/scoring.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/scoring.py rename to assessments/CX_AI_Diagnostic/diaglib/scoring.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/staging.py b/assessments/CX_AI_Diagnostic/diaglib/staging.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/staging.py rename to assessments/CX_AI_Diagnostic/diaglib/staging.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/value_math.py b/assessments/CX_AI_Diagnostic/diaglib/value_math.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/value_math.py rename to assessments/CX_AI_Diagnostic/diaglib/value_math.py diff --git a/studies/202607_CX_AI_Diagnostic/diaglib/visuals.py b/assessments/CX_AI_Diagnostic/diaglib/visuals.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/diaglib/visuals.py rename to assessments/CX_AI_Diagnostic/diaglib/visuals.py diff --git a/studies/202607_CX_AI_Diagnostic/docs/build_spec_v1.md b/assessments/CX_AI_Diagnostic/docs/build_spec_v1.md similarity index 100% rename from studies/202607_CX_AI_Diagnostic/docs/build_spec_v1.md rename to assessments/CX_AI_Diagnostic/docs/build_spec_v1.md diff --git a/studies/202607_CX_AI_Diagnostic/exports/.gitkeep b/assessments/CX_AI_Diagnostic/exports/.gitkeep similarity index 100% rename from studies/202607_CX_AI_Diagnostic/exports/.gitkeep rename to assessments/CX_AI_Diagnostic/exports/.gitkeep diff --git a/studies/202607_CX_AI_Diagnostic/notebooks/diagnostic.ipynb b/assessments/CX_AI_Diagnostic/notebooks/diagnostic.ipynb similarity index 100% rename from studies/202607_CX_AI_Diagnostic/notebooks/diagnostic.ipynb rename to assessments/CX_AI_Diagnostic/notebooks/diagnostic.ipynb diff --git a/studies/202607_CX_AI_Diagnostic/pyproject.toml b/assessments/CX_AI_Diagnostic/pyproject.toml similarity index 100% rename from studies/202607_CX_AI_Diagnostic/pyproject.toml rename to assessments/CX_AI_Diagnostic/pyproject.toml diff --git a/studies/202607_CX_AI_Diagnostic/scripts/build_notebook.py b/assessments/CX_AI_Diagnostic/scripts/build_notebook.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/scripts/build_notebook.py rename to assessments/CX_AI_Diagnostic/scripts/build_notebook.py diff --git a/studies/202607_CX_AI_Diagnostic/scripts/export_report.py b/assessments/CX_AI_Diagnostic/scripts/export_report.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/scripts/export_report.py rename to assessments/CX_AI_Diagnostic/scripts/export_report.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/conftest.py b/assessments/CX_AI_Diagnostic/tests/conftest.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/conftest.py rename to assessments/CX_AI_Diagnostic/tests/conftest.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/test_config.py b/assessments/CX_AI_Diagnostic/tests/test_config.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/test_config.py rename to assessments/CX_AI_Diagnostic/tests/test_config.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/test_export.py b/assessments/CX_AI_Diagnostic/tests/test_export.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/test_export.py rename to assessments/CX_AI_Diagnostic/tests/test_export.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/test_scoring.py b/assessments/CX_AI_Diagnostic/tests/test_scoring.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/test_scoring.py rename to assessments/CX_AI_Diagnostic/tests/test_scoring.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/test_staging.py b/assessments/CX_AI_Diagnostic/tests/test_staging.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/test_staging.py rename to assessments/CX_AI_Diagnostic/tests/test_staging.py diff --git a/studies/202607_CX_AI_Diagnostic/tests/test_value_math.py b/assessments/CX_AI_Diagnostic/tests/test_value_math.py similarity index 100% rename from studies/202607_CX_AI_Diagnostic/tests/test_value_math.py rename to assessments/CX_AI_Diagnostic/tests/test_value_math.py diff --git a/studies/202607_CX_Discovery_Workshop/README.md b/assessments/CX_Discovery_Workshop/README.md similarity index 54% rename from studies/202607_CX_Discovery_Workshop/README.md rename to assessments/CX_Discovery_Workshop/README.md index 6557252..350f4d9 100644 --- a/studies/202607_CX_Discovery_Workshop/README.md +++ b/assessments/CX_Discovery_Workshop/README.md @@ -1,6 +1,8 @@ -# 202607 — CX Exploration & Discovery Workshop +# CX Exploration & Discovery Workshop -A **live facilitation aid** for a CX discovery session, built on the +An **Assessment** — a reusable workshop master (see +[`docs/Assessment_Pattern_V1-00.md`](../../docs/Assessment_Pattern_V1-00.md)) +— and a **live facilitation aid** for a CX discovery session, built on the [Mercury Notebook Deliverable Pattern](../../docs/Mercury_Notebook_Pattern_V1-00.md). Unlike the TEI business-case studies, this deliverable computes no dollars — its "numbers" are **topic status and progress**. The Mercury stage is the @@ -32,36 +34,44 @@ structured form of the source survey. ## Where the content lives: in the notebook -The topic bank — every topic, sub-topic, and facilitator prompt — lives in -the **`topic-bank` cell** of -[`notebooks/cx_discovery.ipynb`](notebooks/cx_discovery.ipynb) (the code cell -tagged `topic-bank`, right under the title). **Content is edited there, in -Jupyter — never in a `.py` file.** It is the study's verbatim anchor: wording -tracks the source survey -[`docs/cx_discovery_survey.md`](docs/cx_discovery_survey.md) (the original -`cxxm.md`), and the `key` slugs are stable identities the sidebar widgets, -captured notes, and JSON export all key off — never renumber or rename them -casually. The cell's own comment block carries the full editing rules. +Two tagged cells of +[`notebooks/cx_discovery.ipynb`](notebooks/cx_discovery.ipynb) carry +everything a consultant edits — **in Jupyter, never in a `.py` file**: + +- **`topic-bank`** — the workshop content: every topic, sub-topic, and + facilitator prompt. It is the study's verbatim anchor: wording tracks the + source survey [`docs/cx_discovery_survey.md`](docs/cx_discovery_survey.md) + (the original `cxxm.md`), and the `key` slugs are stable identities the + sidebar widgets, captured notes, and JSON export all key off — never + renumber or rename them casually. The cell's own comment block carries the + full editing rules. +- **`engagement-data`** — the client facts (client, workshop date, + facilitator, attendees). **Placeholders in this master**; filled in the + engagement copy. The values flow to the stage board header, the export + preamble, and the JSON appendix `meta`. `discoverylib/` holds **code only**: the `Topic`/`SubTopic` schema, the status vocabulary, and the session engine — every engine function takes the bank as its first argument. The ~42 sidebar widgets are built by a runtime loop over `TOPICS`, so board, checklist, script, gate, and export all pick up -a content edit automatically. The test suite reads the tagged cell straight -out of the notebook (no kernel) and pins the content, so `pytest` guards the -bank exactly as shipped. +a content edit automatically. The test suite reads the tagged cells straight +out of the notebook (no kernel) and pins content shape, so `pytest` guards +the bank exactly as shipped. ## Layout ``` -notebooks/cx_discovery.ipynb # THE deliverable — content (topic-bank cell) + presentation +notebooks/cx_discovery.ipynb # THE deliverable — content + data + presentation + # tagged cells: topic-bank · engagement-data · + # presentation ×4 · gate · data-appendix + # stage shows ONLY: title · progress line · board discoverylib/ # the engine — code only, no content session.py # Topic/SubTopic schema, status vocabulary, progress, export payload - staging.py # stage/backstage detection (copied verbatim) -scripts/export_report.py # nbconvert → exports/*.html + *.md -tests/ # content pins (read from the notebook) + engine pins + staging test + staging.py # stage/backstage detection (+ backstage_md for the appendix) +scripts/export_report.py # execute once → exports/*.html + LLM-ready *.md +tests/ # content + engagement pins (read from the notebook), engine pins, staging docs/cx_discovery_survey.md # source survey (the original cxxm.md) -exports/ # generated report sources +exports/ # generated report sources (never committed) ``` ## Run @@ -72,11 +82,17 @@ pip install -e ".[dev]" mercury --working-dir . # serve the stage (share this screen) jupyter lab # analyst / facilitator view -pytest # content pins + engine pins + stage/backstage +pytest # content + engagement + engine + staging pins jupyter nbconvert --to notebook --execute --inplace notebooks/cx_discovery.ipynb # gate python scripts/export_report.py # exports/*.html + *.md for the LLM handoff ``` +The `.md` export opens with a generated preamble (what the document is, the +engagement line, how to read it) and ends with the **Session state (JSON)** +block — the machine source of truth for the write-up. Presentation cells +(setup, the facilitator run-book, widgets, board) are stripped from it; the +HTML export keeps the full presentation for human review. + ## Extending New or reshaped discovery content is an edit to the notebook's `topic-bank` @@ -92,3 +108,9 @@ Add a topic and the sidebar controls, board, checklist, script, gate, and export all pick it up — they're all derived from `TOPICS` at runtime. New *logic* (not content) goes in `discoverylib/session.py` with pins in `tests/test_session.py`. + +**Running this for a client?** Don't fill client data into this master — +follow the copy-out checklist in +[`docs/Assessment_Pattern_V1-00.md`](../../docs/Assessment_Pattern_V1-00.md): +copy the directory out of Palladium, stamp it `YYYYMM_Client_CX_Discovery`, +fill the `engagement-data` cell there, and treat the copy as confidential. diff --git a/studies/202607_CX_Discovery_Workshop/config.toml b/assessments/CX_Discovery_Workshop/config.toml similarity index 77% rename from studies/202607_CX_Discovery_Workshop/config.toml rename to assessments/CX_Discovery_Workshop/config.toml index c7cae25..3718e49 100644 --- a/studies/202607_CX_Discovery_Workshop/config.toml +++ b/assessments/CX_Discovery_Workshop/config.toml @@ -5,18 +5,18 @@ [main] title = "CX Discovery Workshop" favicon_emoji = "🧭" -footer = "CX Exploration & Discovery Workshop" +footer = "CX Exploration & Discovery Workshop · NTT DATA" notebooks_button_label = "Workshops" [welcome] header = "CX Discovery Workshop" message = """ -The live visual for a CX exploration & discovery session. Topics and -progress render on screen for the client; the facilitator drives the -question script and captures notes backstage. Mark each topic's status in -the sidebar as the conversation moves — the board and the progress bar -update live. Afterward, export the captured notes for drafting with -`python scripts/export_report.py`. +The live visual for a CX exploration & discovery session — open +**cx_discovery.ipynb**. Topics and progress render on screen for the +client; the facilitator drives the question script and captures notes +backstage. Mark each topic's status in the sidebar as the conversation +moves — the board and the progress bar update live. Afterward, export the +captured notes for drafting with `python scripts/export_report.py`. """ [theme] @@ -46,6 +46,12 @@ focus_border_color = "#0072bc" hover_background_color = "#eef5fb" selected_background_color = "#dcecfa" +# ── State colors — brand Success / Warning / Error (docs/brand.md) ── +success_color = "#00cb5d" +warning_color = "#ffc400" +danger_color = "#e42600" +slider_track_color = "#e2e6e9" + # ── Sidebar — clean white, hairline divider ── sidebar_background_color = "#ffffff" sidebar_text_color = "#2e404d" diff --git a/studies/202607_CX_Discovery_Workshop/discoverylib/__init__.py b/assessments/CX_Discovery_Workshop/discoverylib/__init__.py similarity index 93% rename from studies/202607_CX_Discovery_Workshop/discoverylib/__init__.py rename to assessments/CX_Discovery_Workshop/discoverylib/__init__.py index c9ffe51..ac60601 100644 --- a/studies/202607_CX_Discovery_Workshop/discoverylib/__init__.py +++ b/assessments/CX_Discovery_Workshop/discoverylib/__init__.py @@ -40,9 +40,9 @@ from .session import ( subtopic_checklist, subtopic_id, ) -from .staging import backstage, on_stage +from .staging import backstage, backstage_md, on_stage -__version__ = "0.2.0" +__version__ = "0.3.0" __all__ = [ # schema @@ -55,5 +55,5 @@ __all__ = [ "ChecklistItem", "subtopic_checklist", "subtopic_id", "TopicState", "build_session", "session_json", # staging - "on_stage", "backstage", + "on_stage", "backstage", "backstage_md", ] diff --git a/studies/202607_CX_Discovery_Workshop/discoverylib/session.py b/assessments/CX_Discovery_Workshop/discoverylib/session.py similarity index 95% rename from studies/202607_CX_Discovery_Workshop/discoverylib/session.py rename to assessments/CX_Discovery_Workshop/discoverylib/session.py index 1cb0190..f50cc11 100644 --- a/studies/202607_CX_Discovery_Workshop/discoverylib/session.py +++ b/assessments/CX_Discovery_Workshop/discoverylib/session.py @@ -196,6 +196,9 @@ class TopicState: notes: str subtopics_total: int subtopics_done: int + # The covered sub-topic KEYS in canonical bank order — the export must + # say WHICH threads were discussed, not just how many. + subtopics_covered: tuple[str, ...] = () def build_session( @@ -216,8 +219,9 @@ def build_session( topic_states: list[TopicState] = [] for t in topics: status = normalize_status(status_by_topic.get(t.key)) - done = sum( - 1 for st in t.subtopics if subtopic_id(t.key, st.key) in done_subtopics + covered = tuple( + st.key for st in t.subtopics + if subtopic_id(t.key, st.key) in done_subtopics ) topic_states.append( TopicState( @@ -231,7 +235,8 @@ def build_session( color=STATUS_COLOR[status], notes=(notes_by_topic.get(t.key) or "").strip(), subtopics_total=len(t.subtopics), - subtopics_done=done, + subtopics_done=len(covered), + subtopics_covered=covered, ) ) @@ -255,7 +260,7 @@ def session_json( appendix does.""" prog: Progress = session["progress"] return { - "study": "202607_CX_Discovery_Workshop", + "assessment": "CX_Discovery_Workshop", "instrument": "CX Exploration & Discovery Workshop", "meta": meta or {}, "progress": { @@ -278,6 +283,7 @@ def session_json( "minutes": ts.minutes, "subtopics_done": ts.subtopics_done, "subtopics_total": ts.subtopics_total, + "subtopics_covered": list(ts.subtopics_covered), "notes": ts.notes, } for ts in session["topics"] diff --git a/studies/202607_CX_Discovery_Workshop/discoverylib/staging.py b/assessments/CX_Discovery_Workshop/discoverylib/staging.py similarity index 64% rename from studies/202607_CX_Discovery_Workshop/discoverylib/staging.py rename to assessments/CX_Discovery_Workshop/discoverylib/staging.py index 5fe72dc..ad8b504 100644 --- a/studies/202607_CX_Discovery_Workshop/discoverylib/staging.py +++ b/assessments/CX_Discovery_Workshop/discoverylib/staging.py @@ -28,3 +28,21 @@ def backstage(*args: object, **kwargs: Any) -> None: """``print`` that renders only backstage (JupyterLab, nbconvert).""" if not on_stage(): print(*args, **kwargs) + + +def backstage_md(text: str) -> None: + """Markdown that renders only backstage (JupyterLab, nbconvert). + + Emitted as a ``text/markdown`` display, so nbconvert's markdown export + carries it verbatim — a stream ``print`` would be indented as a code + block. Falls back to ``print`` when IPython isn't importable (plain + pytest), where ``display`` itself already degrades to ``print``. + """ + if on_stage(): + return + try: + from IPython.display import Markdown, display + except ImportError: + print(text) + return + display(Markdown(text)) # type: ignore[no-untyped-call] diff --git a/studies/202607_CX_Discovery_Workshop/docs/board_preview.png b/assessments/CX_Discovery_Workshop/docs/board_preview.png similarity index 100% rename from studies/202607_CX_Discovery_Workshop/docs/board_preview.png rename to assessments/CX_Discovery_Workshop/docs/board_preview.png diff --git a/studies/202607_CX_Discovery_Workshop/docs/cx_discovery_survey.md b/assessments/CX_Discovery_Workshop/docs/cx_discovery_survey.md similarity index 100% rename from studies/202607_CX_Discovery_Workshop/docs/cx_discovery_survey.md rename to assessments/CX_Discovery_Workshop/docs/cx_discovery_survey.md diff --git a/studies/202607_CX_Discovery_Workshop/exports/.gitkeep b/assessments/CX_Discovery_Workshop/exports/.gitkeep similarity index 100% rename from studies/202607_CX_Discovery_Workshop/exports/.gitkeep rename to assessments/CX_Discovery_Workshop/exports/.gitkeep diff --git a/studies/202607_CX_Discovery_Workshop/notebooks/cx_discovery.ipynb b/assessments/CX_Discovery_Workshop/notebooks/cx_discovery.ipynb similarity index 93% rename from studies/202607_CX_Discovery_Workshop/notebooks/cx_discovery.ipynb rename to assessments/CX_Discovery_Workshop/notebooks/cx_discovery.ipynb index a662b17..47117f0 100644 --- a/studies/202607_CX_Discovery_Workshop/notebooks/cx_discovery.ipynb +++ b/assessments/CX_Discovery_Workshop/notebooks/cx_discovery.ipynb @@ -7,27 +7,8 @@ "source": [ "# CX Exploration & Discovery Workshop\n", "\n", - "The live visual for a discovery session. On screen the client sees the\n", - "**topic board** — each topic, its one-line scope, and a status glyph — the\n", - "**live sub-topic checklist** for whatever we're discussing now, and a\n", - "**progress headline** (\"3/8 topics complete\"). You drive the conversation\n", - "from the question script (which stays backstage); the client is never shown\n", - "a wall of questions.\n", - "\n", - "**This notebook is the deliverable.** Serve it with\n", - "`mercury --working-dir .` and share that screen; mark each topic's status in\n", - "the sidebar as the discussion moves and tick sub-topics as you cover them —\n", - "the board and progress bar update live. Capture answers in the per-topic\n", - "notes boxes. Afterward, `python scripts/export_report.py` writes an\n", - "LLM-readable report source (notes + status + progress as markdown and JSON)\n", - "to feed the survey write-up or a downstream business-case study.\n", - "\n", - "The content — every topic, sub-topic, and facilitator prompt — lives in the\n", - "tagged **topic-bank cell** below: edit it here, in the notebook.\n", - "`discoverylib/` holds only the schema and session logic.\n", - "\n", - "Confidence legend: 🟢 confirmed · 🟡 estimated · 🔴 unknown — used in the\n", - "captured notes, not on the client-facing board." + "A structured conversation across eight topics. The board below tracks where\n", + "the discussion is and what's been covered — it updates live as we talk." ] }, { @@ -36,11 +17,14 @@ "id": "0e66bf69", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.733369Z", - "iopub.status.busy": "2026-07-25T18:27:37.733308Z", - "iopub.status.idle": "2026-07-25T18:27:37.770429Z", - "shell.execute_reply": "2026-07-25T18:27:37.770169Z" - } + "iopub.execute_input": "2026-07-31T13:00:28.748989Z", + "iopub.status.busy": "2026-07-31T13:00:28.748928Z", + "iopub.status.idle": "2026-07-31T13:00:28.788617Z", + "shell.execute_reply": "2026-07-31T13:00:28.788325Z" + }, + "tags": [ + "presentation" + ] }, "outputs": [], "source": [ @@ -61,7 +45,7 @@ " STATUSES, STATUS_LABEL,\n", " build_session, subtopic_checklist, subtopic_id, session_json,\n", ")\n", - "from discoverylib.staging import backstage\n", + "from discoverylib.staging import backstage, backstage_md\n", "\n", "# ── Brand palette (docs/brand.md, light theme) ─────────────────────\n", "NAVY, INK, MUTED = \"#151d2c\", \"#2e404d\", \"#586671\"\n", @@ -78,13 +62,81 @@ { "cell_type": "code", "execution_count": 2, + "id": "13ec330d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-31T13:00:28.789595Z", + "iopub.status.busy": "2026-07-31T13:00:28.789530Z", + "iopub.status.idle": "2026-07-31T13:00:28.791928Z", + "shell.execute_reply": "2026-07-31T13:00:28.791690Z" + }, + "tags": [ + "presentation" + ] + }, + "outputs": [ + { + "data": { + "text/markdown": [ + "**How to run this session** *(backstage — the client never sees this)*\n", + "\n", + "- **Sidebar** — one **status** selector and a **notes** box per topic, plus a\n", + " **checkbox** per sub-topic. Set the topic you're on to *In progress*, tick\n", + " sub-topics as you cover them, mark it *Complete* (or *Skipped*) when you move\n", + " on, and jot answers in its notes box. Every change re-renders the board and\n", + " progress bar (Mercury re-runs the cells below the widgets).\n", + "- **Stage** (the Mercury screen) — the client sees the title, the topic board,\n", + " and the one-line progress print. Nothing else on this page renders there.\n", + "- **Content** lives in the tagged `topic-bank` cell below; client session facts\n", + " in the tagged `engagement-data` cell — placeholders in this master, filled in\n", + " the engagement copy (see docs/Assessment_Pattern_V1-00.md, copy-out checklist).\n", + "- **The question script** renders further down, backstage only — your running\n", + " order; ask conversationally.\n", + "- **Afterward** — `python scripts/export_report.py` writes the LLM-ready\n", + " captured record to `exports/`.\n", + "- Confidence legend for notes: 🟢 confirmed · 🟡 estimated · 🔴 unknown.\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# ── Facilitator run-book — renders backstage only, never on the stage ─\n", + "backstage_md(\"\"\"\\\n", + "**How to run this session** *(backstage — the client never sees this)*\n", + "\n", + "- **Sidebar** — one **status** selector and a **notes** box per topic, plus a\n", + " **checkbox** per sub-topic. Set the topic you're on to *In progress*, tick\n", + " sub-topics as you cover them, mark it *Complete* (or *Skipped*) when you move\n", + " on, and jot answers in its notes box. Every change re-renders the board and\n", + " progress bar (Mercury re-runs the cells below the widgets).\n", + "- **Stage** (the Mercury screen) — the client sees the title, the topic board,\n", + " and the one-line progress print. Nothing else on this page renders there.\n", + "- **Content** lives in the tagged `topic-bank` cell below; client session facts\n", + " in the tagged `engagement-data` cell — placeholders in this master, filled in\n", + " the engagement copy (see docs/Assessment_Pattern_V1-00.md, copy-out checklist).\n", + "- **The question script** renders further down, backstage only — your running\n", + " order; ask conversationally.\n", + "- **Afterward** — `python scripts/export_report.py` writes the LLM-ready\n", + " captured record to `exports/`.\n", + "- Confidence legend for notes: 🟢 confirmed · 🟡 estimated · 🔴 unknown.\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, "id": "4798dd67", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.771619Z", - "iopub.status.busy": "2026-07-25T18:27:37.771553Z", - "iopub.status.idle": "2026-07-25T18:27:37.778335Z", - "shell.execute_reply": "2026-07-25T18:27:37.778043Z" + "iopub.execute_input": "2026-07-31T13:00:28.792635Z", + "iopub.status.busy": "2026-07-31T13:00:28.792572Z", + "iopub.status.idle": "2026-07-31T13:00:28.798532Z", + "shell.execute_reply": "2026-07-31T13:00:28.798266Z" }, "tags": [ "topic-bank" @@ -483,50 +535,67 @@ ] }, { - "cell_type": "markdown", - "id": "b2bd192b", - "metadata": {}, + "cell_type": "code", + "execution_count": 4, + "id": "6140ecae", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-31T13:00:28.799174Z", + "iopub.status.busy": "2026-07-31T13:00:28.799109Z", + "iopub.status.idle": "2026-07-31T13:00:28.800536Z", + "shell.execute_reply": "2026-07-31T13:00:28.800362Z" + }, + "tags": [ + "engagement-data" + ] + }, + "outputs": [], "source": [ - "## How to run this session\n", - "\n", - "- **Sidebar** — one **status** selector and a **notes** box per topic, plus a\n", - " **sub-topic checkbox** for each thread. As the conversation moves, set the\n", - " topic you're on to *In progress*, tick sub-topics as you cover them, mark\n", - " the topic *Complete* (or *Skipped*) when you move on, and jot answers in its\n", - " notes box.\n", - "- **Stage** (this page) — the client-facing board and progress bar below\n", - " re-render on every change.\n", - "- **Backstage** (JupyterLab / the export) — the facilitator question script and\n", - " the captured-notes appendix. Neither shows on the Mercury stage." + "# ── Engagement data — EDIT PER ENGAGEMENT (tagged: engagement-data) ──\n", + "# Client facts live HERE, in the notebook — never in a .py file. In the\n", + "# MASTER these are placeholders; fill them in the engagement copy (see\n", + "# docs/Assessment_Pattern_V1-00.md, \"Copy-out checklist\"). Values flow to\n", + "# the stage board header, the export preamble, and the JSON appendix meta.\n", + "# Keep this cell self-contained (no imports, no magics): tests read it by\n", + "# its \"engagement-data\" tag and exec it.\n", + "ENGAGEMENT: dict[str, object] = {\n", + " \"client\": \"\", # e.g. \"Acme Corp\" — shown on the stage board header\n", + " \"workshop_date\": \"\", # ISO date, e.g. \"2026-08-12\"\n", + " \"facilitator\": \"\", # who runs the session\n", + " \"attendees\": (), # tuple of \"Name — role\" strings\n", + "}" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "e84c36b2", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.779069Z", - "iopub.status.busy": "2026-07-25T18:27:37.779007Z", - "iopub.status.idle": "2026-07-25T18:27:37.811676Z", - "shell.execute_reply": "2026-07-25T18:27:37.811411Z" - } + "iopub.execute_input": "2026-07-31T13:00:28.801204Z", + "iopub.status.busy": "2026-07-31T13:00:28.801148Z", + "iopub.status.idle": "2026-07-31T13:00:28.830970Z", + "shell.execute_reply": "2026-07-31T13:00:28.830721Z" + }, + "tags": [ + "presentation" + ] }, "outputs": [ { "data": { "application/mercury+json": { - "model_id": "a451eea831e647c397da7b1450a4a92f", + "model_id": "a6520b5d0ea3476da951c3f026ef46ee", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "a451eea831e647c397da7b1450a4a92f", + "model_id": "a6520b5d0ea3476da951c3f026ef46ee", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -535,17 +604,17 @@ { "data": { "application/mercury+json": { - "model_id": "4577562ceab14ac59502bf3d61b3558b", + "model_id": "25d50932b435444f925055818eb1e7fb", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "4577562ceab14ac59502bf3d61b3558b", + "model_id": "25d50932b435444f925055818eb1e7fb", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -554,17 +623,17 @@ { "data": { "application/mercury+json": { - "model_id": "d04723c27a694c48965d347729954375", + "model_id": "6eb6e4b2535949e6bdc2ef86f27fa7d5", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "d04723c27a694c48965d347729954375", + "model_id": "6eb6e4b2535949e6bdc2ef86f27fa7d5", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -573,17 +642,17 @@ { "data": { "application/mercury+json": { - "model_id": "96e8b19e7f334299a6b55590bb9161b2", + "model_id": "74c803b80eb749348f9215c7f0bf1ae9", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "96e8b19e7f334299a6b55590bb9161b2", + "model_id": "74c803b80eb749348f9215c7f0bf1ae9", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -592,17 +661,17 @@ { "data": { "application/mercury+json": { - "model_id": "6d51502a805c4ad0a84412e31231a8dc", + "model_id": "7302bae9ac134fe891f312ef56eaffd9", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "6d51502a805c4ad0a84412e31231a8dc", + "model_id": "7302bae9ac134fe891f312ef56eaffd9", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -611,17 +680,17 @@ { "data": { "application/mercury+json": { - "model_id": "d1022625168744a897229cff827f8392", + "model_id": "c531a01e2b5445a685aa9de95bd82d51", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "d1022625168744a897229cff827f8392", + "model_id": "c531a01e2b5445a685aa9de95bd82d51", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -630,17 +699,17 @@ { "data": { "application/mercury+json": { - "model_id": "d5d6f1aa4fbd409699530cc1bca9b74d", + "model_id": "fc9718f4007145fcb6382bb421ab30e4", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "d5d6f1aa4fbd409699530cc1bca9b74d", + "model_id": "fc9718f4007145fcb6382bb421ab30e4", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -649,17 +718,17 @@ { "data": { "application/mercury+json": { - "model_id": "f31201923cb44e409e1ea78501f97681", + "model_id": "8f091c414a234436990cbabb0d247de9", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "f31201923cb44e409e1ea78501f97681", + "model_id": "8f091c414a234436990cbabb0d247de9", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -668,17 +737,17 @@ { "data": { "application/mercury+json": { - "model_id": "222761ab30e745dda9d6be0c8e00b184", + "model_id": "4fffa51b1f8c443a9a54dd5384c62dc2", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "222761ab30e745dda9d6be0c8e00b184", + "model_id": "4fffa51b1f8c443a9a54dd5384c62dc2", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -687,17 +756,17 @@ { "data": { "application/mercury+json": { - "model_id": "3624da047b62467b90bdc02c51190aea", + "model_id": "75964ed9b3bc41b392f597d3d1de7732", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "3624da047b62467b90bdc02c51190aea", + "model_id": "75964ed9b3bc41b392f597d3d1de7732", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -706,17 +775,17 @@ { "data": { "application/mercury+json": { - "model_id": "f669b4e9c5164f8fb734f4f3060e19ea", + "model_id": "347797bec7614a4bb25d2d85224ee854", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "f669b4e9c5164f8fb734f4f3060e19ea", + "model_id": "347797bec7614a4bb25d2d85224ee854", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -725,17 +794,17 @@ { "data": { "application/mercury+json": { - "model_id": "dafa8c55991946a187fe9d550de53414", + "model_id": "af6a4133d4f5443d9906ec19d6a5a322", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "dafa8c55991946a187fe9d550de53414", + "model_id": "af6a4133d4f5443d9906ec19d6a5a322", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -744,17 +813,17 @@ { "data": { "application/mercury+json": { - "model_id": "1b8da4d7656043a789290d8108706dbe", + "model_id": "7ed6ace9dd0c4cd5add9b62128ad94ae", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "1b8da4d7656043a789290d8108706dbe", + "model_id": "7ed6ace9dd0c4cd5add9b62128ad94ae", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -763,17 +832,17 @@ { "data": { "application/mercury+json": { - "model_id": "70f6f83bca334c7688b05d8a09764cb6", + "model_id": "f124bb0f618046d59879ddfe613b2d57", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "70f6f83bca334c7688b05d8a09764cb6", + "model_id": "f124bb0f618046d59879ddfe613b2d57", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -782,17 +851,17 @@ { "data": { "application/mercury+json": { - "model_id": "2defe01408ac4dca857a667365f4a588", + "model_id": "2f11bd348ef442a2b3fb5829f37efe2e", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "2defe01408ac4dca857a667365f4a588", + "model_id": "2f11bd348ef442a2b3fb5829f37efe2e", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -801,17 +870,17 @@ { "data": { "application/mercury+json": { - "model_id": "dfdc4e514e694492a37dcc756e7dc5b7", + "model_id": "005fe38674bd4261897d40591aeb793f", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "dfdc4e514e694492a37dcc756e7dc5b7", + "model_id": "005fe38674bd4261897d40591aeb793f", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -820,17 +889,17 @@ { "data": { "application/mercury+json": { - "model_id": "1bfff7ab287c4d8f985e565e10dcf875", + "model_id": "c225acc8664d4dd482792cc9ad451178", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "1bfff7ab287c4d8f985e565e10dcf875", + "model_id": "c225acc8664d4dd482792cc9ad451178", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -839,17 +908,17 @@ { "data": { "application/mercury+json": { - "model_id": "77fc6178e65e4d7ca76c8adb00f2c115", + "model_id": "e1c00f930b2f4fc6bd6e694b1560fe5d", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "77fc6178e65e4d7ca76c8adb00f2c115", + "model_id": "e1c00f930b2f4fc6bd6e694b1560fe5d", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -858,17 +927,17 @@ { "data": { "application/mercury+json": { - "model_id": "8193b4d5cb2b4f32853bc9c057f3af1e", + "model_id": "b1d02643012c4444b6cf899feb8d1fef", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "8193b4d5cb2b4f32853bc9c057f3af1e", + "model_id": "b1d02643012c4444b6cf899feb8d1fef", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -877,17 +946,17 @@ { "data": { "application/mercury+json": { - "model_id": "03d1b54031a74239931eb8cecd367d12", + "model_id": "eeb51c493f684fd4b4469c9bc27f7bef", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "03d1b54031a74239931eb8cecd367d12", + "model_id": "eeb51c493f684fd4b4469c9bc27f7bef", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -896,17 +965,17 @@ { "data": { "application/mercury+json": { - "model_id": "b3115751d9fa4beabb2f152ed21a0ae3", + "model_id": "26af1dec8a404838beb37187857e4941", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "b3115751d9fa4beabb2f152ed21a0ae3", + "model_id": "26af1dec8a404838beb37187857e4941", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -915,17 +984,17 @@ { "data": { "application/mercury+json": { - "model_id": "8368207a3d684c9b955efb9922b8e7b9", + "model_id": "e611c2d7646447f29b4d9d4b5bf5fa73", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "8368207a3d684c9b955efb9922b8e7b9", + "model_id": "e611c2d7646447f29b4d9d4b5bf5fa73", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -934,17 +1003,17 @@ { "data": { "application/mercury+json": { - "model_id": "fc04710798de44fb99bc55b34300c448", + "model_id": "63ff21a9bdb649cc9d2e4b7d58baffec", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "fc04710798de44fb99bc55b34300c448", + "model_id": "63ff21a9bdb649cc9d2e4b7d58baffec", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -953,17 +1022,17 @@ { "data": { "application/mercury+json": { - "model_id": "f0d9c25925fc45a8b1005234f675ace8", + "model_id": "2a193c9dfd6a4134aaa5826a9f720d80", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "f0d9c25925fc45a8b1005234f675ace8", + "model_id": "2a193c9dfd6a4134aaa5826a9f720d80", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -972,17 +1041,17 @@ { "data": { "application/mercury+json": { - "model_id": "35e7d1e45a5d491bad1e9fdb73b239ff", + "model_id": "06a9529c7c85454195da078934279080", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "35e7d1e45a5d491bad1e9fdb73b239ff", + "model_id": "06a9529c7c85454195da078934279080", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -991,17 +1060,17 @@ { "data": { "application/mercury+json": { - "model_id": "2fa11043750242e997bcb8acebb2b4aa", + "model_id": "b568a0ea3a574d71b72e8095ed2b92e2", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "2fa11043750242e997bcb8acebb2b4aa", + "model_id": "b568a0ea3a574d71b72e8095ed2b92e2", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1010,17 +1079,17 @@ { "data": { "application/mercury+json": { - "model_id": "a8206b8bd1694936af419af888e57e84", + "model_id": "70026e6c871a4a6295b2999e512e77e8", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "a8206b8bd1694936af419af888e57e84", + "model_id": "70026e6c871a4a6295b2999e512e77e8", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1029,17 +1098,17 @@ { "data": { "application/mercury+json": { - "model_id": "59d4a779c9eb46a69d092e98bcd5bc3e", + "model_id": "77865f023ab94b3784bf0bfb35eec22b", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "59d4a779c9eb46a69d092e98bcd5bc3e", + "model_id": "77865f023ab94b3784bf0bfb35eec22b", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1048,17 +1117,17 @@ { "data": { "application/mercury+json": { - "model_id": "020ff59743bc4ba384a216439c71fb35", + "model_id": "c5b373ec5b224d76b5881b2da681859d", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "020ff59743bc4ba384a216439c71fb35", + "model_id": "c5b373ec5b224d76b5881b2da681859d", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1067,17 +1136,17 @@ { "data": { "application/mercury+json": { - "model_id": "b72bd8acfd374276b0ebdd203075c9b6", + "model_id": "a250beba66ae4e149f0daa8c353627c7", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "b72bd8acfd374276b0ebdd203075c9b6", + "model_id": "a250beba66ae4e149f0daa8c353627c7", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1086,17 +1155,17 @@ { "data": { "application/mercury+json": { - "model_id": "d6fef173bf9e42179464b5034b9728e9", + "model_id": "50c76b29ed32468badebe6ff4f9f1bb0", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "d6fef173bf9e42179464b5034b9728e9", + "model_id": "50c76b29ed32468badebe6ff4f9f1bb0", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1105,17 +1174,17 @@ { "data": { "application/mercury+json": { - "model_id": "f0fae9e3698e4246ab05f98f02919d30", + "model_id": "194ecfd03c6d4f24bada37b3d34b9ffe", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "f0fae9e3698e4246ab05f98f02919d30", + "model_id": "194ecfd03c6d4f24bada37b3d34b9ffe", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1124,17 +1193,17 @@ { "data": { "application/mercury+json": { - "model_id": "8686a07e5bd840f181b8fb1295280bc0", + "model_id": "53587648b3aa431682d06e4dbe401f8d", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "8686a07e5bd840f181b8fb1295280bc0", + "model_id": "53587648b3aa431682d06e4dbe401f8d", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1143,17 +1212,17 @@ { "data": { "application/mercury+json": { - "model_id": "f0aabb7f6d944dfa839728a33422d5c8", + "model_id": "473555ebf30f436db370faa41aeaf04d", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "f0aabb7f6d944dfa839728a33422d5c8", + "model_id": "473555ebf30f436db370faa41aeaf04d", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1162,17 +1231,17 @@ { "data": { "application/mercury+json": { - "model_id": "ea3fc8677a1c4aec8eca4f0bc1f7a5a8", + "model_id": "4ae31f046c87487ba97a08fbcca7cea7", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "ea3fc8677a1c4aec8eca4f0bc1f7a5a8", + "model_id": "4ae31f046c87487ba97a08fbcca7cea7", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1181,17 +1250,17 @@ { "data": { "application/mercury+json": { - "model_id": "6c1b7167e9b1427b88bc6294a069c64e", + "model_id": "ce19eea1122b461e919dd45db8fbbc0a", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "6c1b7167e9b1427b88bc6294a069c64e", + "model_id": "ce19eea1122b461e919dd45db8fbbc0a", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1200,17 +1269,17 @@ { "data": { "application/mercury+json": { - "model_id": "ee709981f4454a96b98a9c9bb1e6d4ca", + "model_id": "2802b92392a5461394c542372ec3681f", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "ee709981f4454a96b98a9c9bb1e6d4ca", + "model_id": "2802b92392a5461394c542372ec3681f", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1219,17 +1288,17 @@ { "data": { "application/mercury+json": { - "model_id": "168c33d8a28848079cdc88aa3d753a92", + "model_id": "6a3c639539274c93913e11eaba5528b6", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "168c33d8a28848079cdc88aa3d753a92", + "model_id": "6a3c639539274c93913e11eaba5528b6", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1238,17 +1307,17 @@ { "data": { "application/mercury+json": { - "model_id": "d980c0367d404f7e9397b9b37021ffbd", + "model_id": "7c46a99022614830981147c007b7b82a", "position": "sidebar", "widget": "SelectWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "d980c0367d404f7e9397b9b37021ffbd", + "model_id": "7c46a99022614830981147c007b7b82a", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1257,17 +1326,17 @@ { "data": { "application/mercury+json": { - "model_id": "ca66db1b382a49d4af0b4ce9593e6e70", + "model_id": "5d4d52998a594074bd935b1a8105eed9", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "ca66db1b382a49d4af0b4ce9593e6e70", + "model_id": "5d4d52998a594074bd935b1a8105eed9", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1276,17 +1345,17 @@ { "data": { "application/mercury+json": { - "model_id": "37d2712fe6274dfab2b11a0fcdaea8cd", + "model_id": "007caa3274664331b412c46142fd4f7c", "position": "sidebar", "widget": "CheckboxWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "37d2712fe6274dfab2b11a0fcdaea8cd", + "model_id": "007caa3274664331b412c46142fd4f7c", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1295,17 +1364,17 @@ { "data": { "application/mercury+json": { - "model_id": "6d8dd95bf76f448ca63954842e0c252d", + "model_id": "1914451087144805a0d1d2165f15d07f", "position": "sidebar", "widget": "TextInputWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "6d8dd95bf76f448ca63954842e0c252d", + "model_id": "1914451087144805a0d1d2165f15d07f", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1341,14 +1410,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "24e06a3e", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.812296Z", - "iopub.status.busy": "2026-07-25T18:27:37.812238Z", - "iopub.status.idle": "2026-07-25T18:27:37.814419Z", - "shell.execute_reply": "2026-07-25T18:27:37.814214Z" + "iopub.execute_input": "2026-07-31T13:00:28.831619Z", + "iopub.status.busy": "2026-07-31T13:00:28.831551Z", + "iopub.status.idle": "2026-07-31T13:00:28.833832Z", + "shell.execute_reply": "2026-07-31T13:00:28.833589Z" } }, "outputs": [ @@ -1383,26 +1452,29 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "c6b92da6", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.814991Z", - "iopub.status.busy": "2026-07-25T18:27:37.814934Z", - "iopub.status.idle": "2026-07-25T18:27:37.820348Z", - "shell.execute_reply": "2026-07-25T18:27:37.820155Z" - } + "iopub.execute_input": "2026-07-31T13:00:28.834436Z", + "iopub.status.busy": "2026-07-31T13:00:28.834380Z", + "iopub.status.idle": "2026-07-31T13:00:28.840308Z", + "shell.execute_reply": "2026-07-31T13:00:28.840061Z" + }, + "tags": [ + "presentation" + ] }, "outputs": [ { "data": { "application/mercury+json": { - "model_id": "40f2f5c8dcf34b8eb272fcb0680dfa00", + "model_id": "10562f9890124ae3a6987c9550316eb4", "position": "inline", "widget": "MarkdownWidget" }, "application/vnd.jupyter.widget-view+json": { - "model_id": "40f2f5c8dcf34b8eb272fcb0680dfa00", + "model_id": "10562f9890124ae3a6987c9550316eb4", "version_major": 2, "version_minor": 0 }, @@ -1464,9 +1536,22 @@ "_ACTIVE_TOPIC = SESSION[\"active_topic\"]\n", "_rows = \"\".join(_topic_row(ts, ts.key == SESSION[\"active_topic_key\"])\n", " for ts in SESSION[\"topics\"])\n", + "\n", + "# Engagement sub-line — only when the engagement-data cell is filled in,\n", + "# so the master's board renders exactly as before.\n", + "_engagement_line = \"\"\n", + "if str(ENGAGEMENT.get(\"client\", \"\")).strip():\n", + " _bits = [esc(str(ENGAGEMENT[\"client\"]).strip())]\n", + " if str(ENGAGEMENT.get(\"workshop_date\", \"\")).strip():\n", + " _bits.append(esc(str(ENGAGEMENT[\"workshop_date\"]).strip()))\n", + " _engagement_line = (\n", + " f'
'\n", + " f'{\" · \".join(_bits)}
')\n", + "\n", "_header = (\n", " f'
'\n", " f'CX Discovery — topics
'\n", + " f'{_engagement_line}'\n", " f'
'\n", " f'{len(SESSION[\"topics\"])} topics · ~{SESSION[\"agenda_minutes\"]} minutes
')\n", "\n", @@ -1478,29 +1563,16 @@ "_ = mr.Markdown(text=_board_html)" ] }, - { - "cell_type": "markdown", - "id": "421e6d11", - "metadata": {}, - "source": [ - "## Facilitator question script (backstage)\n", - "\n", - "The prompts below render in JupyterLab and in the exports — **not** on the\n", - "Mercury stage. They are your running order; ask them conversationally and let\n", - "the topics expand or drop as the client answers. Tick each sub-topic in the\n", - "sidebar as you cover it." - ] - }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "d70653b9", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.820966Z", - "iopub.status.busy": "2026-07-25T18:27:37.820910Z", - "iopub.status.idle": "2026-07-25T18:27:37.822712Z", - "shell.execute_reply": "2026-07-25T18:27:37.822507Z" + "iopub.execute_input": "2026-07-31T13:00:28.840935Z", + "iopub.status.busy": "2026-07-31T13:00:28.840872Z", + "iopub.status.idle": "2026-07-31T13:00:28.842720Z", + "shell.execute_reply": "2026-07-31T13:00:28.842508Z" } }, "outputs": [ @@ -1707,30 +1779,20 @@ "backstage(\"\\n\".join(_lines))" ] }, - { - "cell_type": "markdown", - "id": "5fd4bc7f", - "metadata": {}, - "source": [ - "## Verification & assertions\n", - "\n", - "Engine pins use explicit values independent of the sidebar, so the gate tests\n", - "`discoverylib`, not the current session; structural ties hold at **any** widget\n", - "state. This cell must pass under headless `nbconvert --execute` — it is the\n", - "study's smoke test. Output renders backstage only." - ] - }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "cc5c3a6e", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.823280Z", - "iopub.status.busy": "2026-07-25T18:27:37.823225Z", - "iopub.status.idle": "2026-07-25T18:27:37.825928Z", - "shell.execute_reply": "2026-07-25T18:27:37.825722Z" - } + "iopub.execute_input": "2026-07-31T13:00:28.843292Z", + "iopub.status.busy": "2026-07-31T13:00:28.843230Z", + "iopub.status.idle": "2026-07-31T13:00:28.846502Z", + "shell.execute_reply": "2026-07-31T13:00:28.846239Z" + }, + "tags": [ + "gate" + ] }, "outputs": [ { @@ -1760,6 +1822,16 @@ "_assert(SESSION[\"agenda_minutes\"] == 110, \"agenda minutes drifted\")\n", "_assert(set(STATUS_BY_TOPIC) == {t.key for t in TOPICS}, \"status keys ≠ topics\")\n", "\n", + "# Engagement data — SHAPE only. The master ships placeholders and an\n", + "# engagement copy ships real values; both must pass, so never pin\n", + "# emptiness (or a value) here.\n", + "_assert(set(ENGAGEMENT) == {\"client\", \"workshop_date\", \"facilitator\", \"attendees\"},\n", + " \"engagement-data keys drifted\")\n", + "for _k in (\"client\", \"workshop_date\", \"facilitator\"):\n", + " _assert(isinstance(ENGAGEMENT[_k], str), f\"ENGAGEMENT[{_k!r}] must be a string\")\n", + "_assert(all(isinstance(_a, str) for _a in ENGAGEMENT[\"attendees\"]),\n", + " \"attendees must be strings\")\n", + "\n", "# Structural ties — hold at ANY sidebar setting\n", "_p = SESSION[\"progress\"]\n", "_assert(_p.total == len(SESSION[\"topics\"]) == 8, \"progress total ≠ topic count\")\n", @@ -1773,199 +1845,209 @@ "\n", "# Export payload is plain-JSON serializable and consistent with the session\n", "import json as _json\n", - "_payload = session_json(SESSION, meta={\"note\": \"gate check\"})\n", + "_payload = session_json(SESSION, meta=ENGAGEMENT)\n", "_json.dumps(_payload) # raises if not serializable\n", "_assert(_payload[\"progress\"][\"completed\"] == _p.completed, \"export/session mismatch\")\n", "_assert(len(_payload[\"topics\"]) == 8, \"export lost a topic\")\n", + "_assert(_payload[\"assessment\"] == \"CX_Discovery_Workshop\", \"assessment slug drifted\")\n", + "_assert(_payload[\"meta\"][\"client\"] == ENGAGEMENT[\"client\"], \"meta lost the client\")\n", "\n", "backstage(\"All assertions passed.\")\n", "backstage(f\" {_p.label} · {_p.resolved}/{_p.total} resolved\")" ] }, - { - "cell_type": "markdown", - "id": "bb550a85", - "metadata": {}, - "source": [ - "## Data appendix — for the machines\n", - "\n", - "The captured session as a markdown table plus one JSON block of state, so the\n", - "exported report is complete LLM input for drafting the survey write-up or\n", - "seeding a business-case study. Renders **backstage** — hidden on the Mercury\n", - "stage." - ] - }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "ea73c809", "metadata": { "execution": { - "iopub.execute_input": "2026-07-25T18:27:37.826553Z", - "iopub.status.busy": "2026-07-25T18:27:37.826496Z", - "iopub.status.idle": "2026-07-25T18:27:37.828603Z", - "shell.execute_reply": "2026-07-25T18:27:37.828394Z" - } + "iopub.execute_input": "2026-07-31T13:00:28.847168Z", + "iopub.status.busy": "2026-07-31T13:00:28.847109Z", + "iopub.status.idle": "2026-07-31T13:00:28.849573Z", + "shell.execute_reply": "2026-07-31T13:00:28.849320Z" + }, + "tags": [ + "data-appendix" + ] }, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "#### Captured session\n", - "\n", - "| Topic | Status | Sub-topics | Notes |\n", - "|---|---|---:|---|\n", - "| Background & Organization | Not started | 0/5 | — |\n", - "| CX Strategy | Not started | 0/5 | — |\n", - "| Channels | Not started | 0/5 | — |\n", - "| Agent & Supervisor Environment | Not started | 0/2 | — |\n", - "| Routing & Automation | Not started | 0/3 | — |\n", - "| Workforce Engagement | Not started | 0/3 | — |\n", - "| Training | Not started | 0/1 | — |\n", - "| Reporting & Insights | Not started | 0/2 | — |\n", - "\n", - "0/8 topics complete · 0/8 resolved · agenda ~110 min\n", - "\n", - "\n", - "#### Session state (JSON)\n", - "\n", - "```json\n", - "{\n", - " \"study\": \"202607_CX_Discovery_Workshop\",\n", - " \"instrument\": \"CX Exploration & Discovery Workshop\",\n", - " \"meta\": {\n", - " \"client\": \"\",\n", - " \"date\": \"\",\n", - " \"facilitator\": \"\"\n", - " },\n", - " \"progress\": {\n", - " \"total_topics\": 8,\n", - " \"completed\": 0,\n", - " \"skipped\": 0,\n", - " \"in_progress\": 0,\n", - " \"not_started\": 8,\n", - " \"fraction_complete\": 0.0,\n", - " \"label\": \"0/8 topics complete\"\n", - " },\n", - " \"agenda_minutes\": 110,\n", - " \"active_topic\": \"background\",\n", - " \"topics\": [\n", - " {\n", - " \"key\": \"background\",\n", - " \"title\": \"Background & Organization\",\n", - " \"scope\": \"Lines of business, channels, org structure, decision-making\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 15,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 5,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"cx_strategy\",\n", - " \"title\": \"CX Strategy\",\n", - " \"scope\": \"How CX value is defined, measured, and organized to deliver\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 15,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 5,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"channels\",\n", - " \"title\": \"Channels\",\n", - " \"scope\": \"Inbound & outbound volumes, metrics, contact reasons\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 20,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 5,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"agent_environment\",\n", - " \"title\": \"Agent & Supervisor Environment\",\n", - " \"scope\": \"Locations, endpoints, desktop, applications, knowledge\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 10,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 2,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"routing_automation\",\n", - " \"title\": \"Routing & Automation\",\n", - " \"scope\": \"Self-service, IVR, speech, virtual agents, agent assist, RPA\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 15,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 3,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"workforce_engagement\",\n", - " \"title\": \"Workforce Engagement\",\n", - " \"scope\": \"Call recording / QM, WFM, agent self-service, integrations\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 20,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 3,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"training\",\n", - " \"title\": \"Training\",\n", - " \"scope\": \"Onboarding, format, assessment, eLearning, QM integration\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 5,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 1,\n", - " \"notes\": \"\"\n", - " },\n", - " {\n", - " \"key\": \"reporting_insights\",\n", - " \"title\": \"Reporting & Insights\",\n", - " \"scope\": \"Real-time & historical reporting, analytics, BI, CRM\",\n", - " \"status\": \"not_started\",\n", - " \"minutes\": 10,\n", - " \"subtopics_done\": 0,\n", - " \"subtopics_total\": 2,\n", - " \"notes\": \"\"\n", - " }\n", - " ]\n", - "}\n", - "```\n" - ] + "data": { + "text/markdown": [ + "## Data appendix — the captured session\n", + "\n", + "| Topic | Status | Sub-topics | Notes |\n", + "|---|---|---:|---|\n", + "| Background & Organization | Not started | 0/5 | — |\n", + "| CX Strategy | Not started | 0/5 | — |\n", + "| Channels | Not started | 0/5 | — |\n", + "| Agent & Supervisor Environment | Not started | 0/2 | — |\n", + "| Routing & Automation | Not started | 0/3 | — |\n", + "| Workforce Engagement | Not started | 0/3 | — |\n", + "| Training | Not started | 0/1 | — |\n", + "| Reporting & Insights | Not started | 0/2 | — |\n", + "\n", + "0/8 topics complete · 0/8 resolved · agenda ~110 min\n", + "\n", + "#### Session state (JSON)\n", + "\n", + "```json\n", + "{\n", + " \"assessment\": \"CX_Discovery_Workshop\",\n", + " \"instrument\": \"CX Exploration & Discovery Workshop\",\n", + " \"meta\": {\n", + " \"client\": \"\",\n", + " \"workshop_date\": \"\",\n", + " \"facilitator\": \"\",\n", + " \"attendees\": []\n", + " },\n", + " \"progress\": {\n", + " \"total_topics\": 8,\n", + " \"completed\": 0,\n", + " \"skipped\": 0,\n", + " \"in_progress\": 0,\n", + " \"not_started\": 8,\n", + " \"fraction_complete\": 0.0,\n", + " \"label\": \"0/8 topics complete\"\n", + " },\n", + " \"agenda_minutes\": 110,\n", + " \"active_topic\": \"background\",\n", + " \"topics\": [\n", + " {\n", + " \"key\": \"background\",\n", + " \"title\": \"Background & Organization\",\n", + " \"scope\": \"Lines of business, channels, org structure, decision-making\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 15,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 5,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"cx_strategy\",\n", + " \"title\": \"CX Strategy\",\n", + " \"scope\": \"How CX value is defined, measured, and organized to deliver\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 15,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 5,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"channels\",\n", + " \"title\": \"Channels\",\n", + " \"scope\": \"Inbound & outbound volumes, metrics, contact reasons\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 20,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 5,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"agent_environment\",\n", + " \"title\": \"Agent & Supervisor Environment\",\n", + " \"scope\": \"Locations, endpoints, desktop, applications, knowledge\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 10,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 2,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"routing_automation\",\n", + " \"title\": \"Routing & Automation\",\n", + " \"scope\": \"Self-service, IVR, speech, virtual agents, agent assist, RPA\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 15,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 3,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"workforce_engagement\",\n", + " \"title\": \"Workforce Engagement\",\n", + " \"scope\": \"Call recording / QM, WFM, agent self-service, integrations\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 20,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 3,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"training\",\n", + " \"title\": \"Training\",\n", + " \"scope\": \"Onboarding, format, assessment, eLearning, QM integration\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 5,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 1,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " },\n", + " {\n", + " \"key\": \"reporting_insights\",\n", + " \"title\": \"Reporting & Insights\",\n", + " \"scope\": \"Real-time & historical reporting, analytics, BI, CRM\",\n", + " \"status\": \"not_started\",\n", + " \"minutes\": 10,\n", + " \"subtopics_done\": 0,\n", + " \"subtopics_total\": 2,\n", + " \"subtopics_covered\": [],\n", + " \"notes\": \"\"\n", + " }\n", + " ]\n", + "}\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ "# ── Data appendix — LLM-readable dump of the captured session ───────\n", - "# Renders backstage only (JupyterLab / nbconvert exports).\n", + "# Renders backstage only (JupyterLab / nbconvert exports) as ONE markdown\n", + "# display, so the .md export carries it verbatim — the captured-session\n", + "# table first, the fenced JSON block LAST: that block is the\n", + "# machine-readable source of truth for the write-up. Engagement identity\n", + "# comes from the engagement-data cell above.\n", "import json as _json\n", "\n", - "# Session metadata — edit these live for the client, or leave as defaults.\n", - "_META = {\n", - " \"client\": \"\", # 🟡 fill in for the engagement\n", - " \"date\": \"\", # 🟡 workshop date\n", - " \"facilitator\": \"\", # 🟡\n", - "}\n", + "_md = [\"## Data appendix — the captured session\\n\"]\n", + "_who = \" · \".join(\n", + " str(ENGAGEMENT[_k]).strip()\n", + " for _k in (\"client\", \"workshop_date\", \"facilitator\")\n", + " if str(ENGAGEMENT.get(_k, \"\")).strip())\n", + "if _who:\n", + " _md.append(f\"_{_who}_\\n\")\n", "\n", - "backstage(\"\\n#### Captured session\\n\")\n", - "_rows = [\"| Topic | Status | Sub-topics | Notes |\",\n", - " \"|---|---|---:|---|\"]\n", + "_md.append(\"| Topic | Status | Sub-topics | Notes |\")\n", + "_md.append(\"|---|---|---:|---|\")\n", "for _ts in SESSION[\"topics\"]:\n", " _note = _ts.notes.replace(\"|\", \"\\\\|\").replace(\"\\n\", \" \") or \"—\"\n", - " _rows.append(f\"| {_ts.title} | {_ts.status_label} | \"\n", - " f\"{_ts.subtopics_done}/{_ts.subtopics_total} | {_note} |\")\n", - "backstage(\"\\n\".join(_rows))\n", + " _md.append(f\"| {_ts.title} | {_ts.status_label} | \"\n", + " f\"{_ts.subtopics_done}/{_ts.subtopics_total} | {_note} |\")\n", "\n", - "backstage(f\"\\n{PROGRESS.label} · {PROGRESS.resolved}/{PROGRESS.total} resolved \"\n", - " f\"· agenda ~{SESSION['agenda_minutes']} min\\n\")\n", + "_md.append(f\"\\n{PROGRESS.label} · {PROGRESS.resolved}/{PROGRESS.total} resolved \"\n", + " f\"· agenda ~{SESSION['agenda_minutes']} min\\n\")\n", "\n", - "backstage(\"\\n#### Session state (JSON)\\n\")\n", - "backstage(\"```json\")\n", - "backstage(_json.dumps(session_json(SESSION, meta=_META), indent=2, ensure_ascii=False))\n", - "backstage(\"```\")" + "_md.append(\"#### Session state (JSON)\\n\")\n", + "_md.append(\"```json\")\n", + "_md.append(_json.dumps(session_json(SESSION, meta=ENGAGEMENT),\n", + " indent=2, ensure_ascii=False))\n", + "_md.append(\"```\")\n", + "\n", + "backstage_md(\"\\n\".join(_md))" ] } ], @@ -1990,7 +2072,7 @@ "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { - "020ff59743bc4ba384a216439c71fb35": { + "005fe38674bd4261897d40591aeb793f": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2010,9 +2092,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "Routing & Automation: Digital assistant apps", + "label": "Channels: Inbound context", "last_changed_at": "", - "layout": "IPY_MODEL_85f77e3f4ac341ea8465de7ed8e60362", + "layout": "IPY_MODEL_dd2e189ff03d4b76bd92e483dbe721ae", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -2024,7 +2106,7 @@ "value": false } }, - "03d1b54031a74239931eb8cecd367d12": { + "007caa3274664331b412c46142fd4f7c": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2044,9 +2126,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "Channels: Outbound", + "label": "Reporting & Insights: Analytics & insights", "last_changed_at": "", - "layout": "IPY_MODEL_52d9e6ee9d2746e69d60cc3af8f5b01d", + "layout": "IPY_MODEL_8319aeb5add740b0b1949c4cbb34bae7", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -2058,7 +2140,7 @@ "value": false } }, - "1491ef06c9ea4b15b3c72de83b38a374": { + "017c33dbf5274f03bc347a8503d97159": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2111,7 +2193,60 @@ "width": null } }, - "168c33d8a28848079cdc88aa3d753a92": { + "041455db212e45ceb2c7b7b29d504825": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "06a9529c7c85454195da078934279080": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2130,8 +2265,8 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "Training — notes", - "layout": "IPY_MODEL_5ea3a44bff7c4ce4a63937abc5abf078", + "label": "Agent & Supervisor Environment — notes", + "layout": "IPY_MODEL_80fca3f575234c3ba087ab24a4e0a1b1", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -2142,7 +2277,35 @@ "value": "" } }, - "194b98c3b1ab4c828036e196974cbbfa": { + "10562f9890124ae3a6987c9550316eb4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "cell_id": "", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d448c02bc9f430a9b33cd4990344fa2", + "layout_path": null, + "placeholder": "​", + "position": "inline", + "render_slot_id": null, + "source_cell_id": null, + "style": "IPY_MODEL_b1181ef7440a47a7916b4367d2e912aa", + "tabbable": null, + "tooltip": null, + "value": "
CX Discovery — topics
8 topics · ~110 minutes
0 of 8 topics complete
Background & Organizationdiscussing now →Not started
Lines of business, channels, org structure, decision-making
  • Lines of business & scope
  • Channels supported
  • Org & reporting structure
  • Decision-making & priorities
  • Problems & causes today
CX StrategyNot started
How CX value is defined, measured, and organized to deliver
ChannelsNot started
Inbound & outbound volumes, metrics, contact reasons
Agent & Supervisor EnvironmentNot started
Locations, endpoints, desktop, applications, knowledge
Routing & AutomationNot started
Self-service, IVR, speech, virtual agents, agent assist, RPA
Workforce EngagementNot started
Call recording / QM, WFM, agent self-service, integrations
TrainingNot started
Onboarding, format, assessment, eLearning, QM integration
Reporting & InsightsNot started
Real-time & historical reporting, analytics, BI, CRM
" + } + }, + "10bcd8e392d44ba787e16984dd019565": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2195,75 +2358,7 @@ "width": null } }, - "1b8da4d7656043a789290d8108706dbe": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "CX Strategy: Employee engagement", - "last_changed_at": "", - "layout": "IPY_MODEL_6a6c72a002a14996806931189dc2a762", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "1bfff7ab287c4d8f985e565e10dcf875": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Channels: Voice & video metrics", - "last_changed_at": "", - "layout": "IPY_MODEL_4bf057077ef54111826b01e09cb0c2d7", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "21e165b51f474b6f857732aa667bb185": { + "174a2effb95041b0b61ca80bf0d5c226": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2316,7 +2411,38 @@ "width": null } }, - "222761ab30e745dda9d6be0c8e00b184": { + "1914451087144805a0d1d2165f15d07f": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Reporting & Insights — notes", + "layout": "IPY_MODEL_6304c720969d412f97d54a7a2b1d2360", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } + }, + "194ecfd03c6d4f24bada37b3d34b9ffe": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2336,9 +2462,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "CX Strategy: Value & strategic position", + "label": "Workforce Engagement: Call recording & Quality Management", "last_changed_at": "", - "layout": "IPY_MODEL_39162785d9954803a67e4053ec694ad9", + "layout": "IPY_MODEL_7cf7689da95d411bbe31b4db1c2d358f", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -2350,7 +2476,352 @@ "value": false } }, - "2defe01408ac4dca857a667365f4a588": { + "1af6c54cb9024baba39d308f1a95480b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "22226cf1a4784274bf258eb89c98afb4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "25d50932b435444f925055818eb1e7fb": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization: Lines of business & scope", + "last_changed_at": "", + "layout": "IPY_MODEL_a3819a7c608e439d970efe9652597785", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "26af1dec8a404838beb37187857e4941": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Channels — notes", + "layout": "IPY_MODEL_017c33dbf5274f03bc347a8503d97159", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } + }, + "2802b92392a5461394c542372ec3681f": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Training: Onboarding & enablement", + "last_changed_at": "", + "layout": "IPY_MODEL_2bc1f73c6512476d89301d6a56ba4b76", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "2a193c9dfd6a4134aaa5826a9f720d80": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Agent & Supervisor Environment: Desktop & applications", + "last_changed_at": "", + "layout": "IPY_MODEL_50deb959f8ae48f88ce5e485a4131fbe", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "2bc1f73c6512476d89301d6a56ba4b76": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2d448c02bc9f430a9b33cd4990344fa2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2f11bd348ef442a2b3fb5829f37efe2e": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2376,7 +2847,7 @@ "disabled": false, "hidden": false, "label": "Channels — status", - "layout": "IPY_MODEL_5a26381ab5a546edb671524ffec3093d", + "layout": "IPY_MODEL_e65e3f4ec0cd4f3b8aef0b06807eb9eb", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -2387,7 +2858,7 @@ "value": "Not started" } }, - "2e29bd2928674e31b3fd1e2d485a29ed": { + "33591c514cf243208a28c314bf94830b": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2440,15 +2911,15 @@ "width": null } }, - "2fa11043750242e997bcb8acebb2b4aa": { + "347797bec7614a4bb25d2d85224ee854": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", "state": { - "_anywidget_id": "mercury.select.SelectWidget", - "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", "_model_module": "anywidget", "_model_module_version": "~0.11.*", "_model_name": "AnyModel", @@ -2456,28 +2927,25 @@ "_view_module": "anywidget", "_view_module_version": "~0.11.*", "_view_name": "AnyView", + "appearance": "box", "cell_id": "", - "choices": [ - "Not started", - "In progress", - "Complete", - "Skipped" - ], "disabled": false, "hidden": false, - "label": "Routing & Automation — status", - "layout": "IPY_MODEL_f756ed14bebb4b1787445e09b080dd1d", + "label": "CX Strategy: Insight & Voice of Customer", + "last_changed_at": "", + "layout": "IPY_MODEL_bc46a9479dd14a5b8e57dc6b35afb458", "layout_path": null, + "n_toggles": 0, "position": "sidebar", "render_slot_id": null, "source_cell_id": null, "tabbable": null, "tooltip": null, "url_key": "", - "value": "Not started" + "value": false } }, - "2fbc83509c604715ba49f524282af285": { + "42d5286c049140cba0bbcd5ffbba2359": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2530,7 +2998,7 @@ "width": null } }, - "31ce043e86034f2c89ab70982964edba": { + "43656bba585e4b8c86168b6eeaea3530": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -2583,7 +3051,94 @@ "width": null } }, - "35e7d1e45a5d491bad1e9fdb73b239ff": { + "46e8479ff738471fb4fce78dc41384c0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "473555ebf30f436db370faa41aeaf04d": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Workforce Engagement: Agent self-service & integrations", + "last_changed_at": "", + "layout": "IPY_MODEL_22226cf1a4784274bf258eb89c98afb4", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "4ae31f046c87487ba97a08fbcca7cea7": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2602,8 +3157,8 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "Agent & Supervisor Environment — notes", - "layout": "IPY_MODEL_21e165b51f474b6f857732aa667bb185", + "label": "Workforce Engagement — notes", + "layout": "IPY_MODEL_8f60dbda5b484579940066e5b0fd26ee", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -2614,7 +3169,7 @@ "value": "" } }, - "3624da047b62467b90bdc02c51190aea": { + "4fffa51b1f8c443a9a54dd5384c62dc2": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -2634,9 +3189,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "CX Strategy: Organization & operating model", + "label": "CX Strategy: Value & strategic position", "last_changed_at": "", - "layout": "IPY_MODEL_5b191db98ba842e59c70203d4442805b", + "layout": "IPY_MODEL_8922090ab38b4984be10659d45650f2e", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -2648,2423 +3203,7 @@ "value": false } }, - "37d2712fe6274dfab2b11a0fcdaea8cd": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Reporting & Insights: Analytics & insights", - "last_changed_at": "", - "layout": "IPY_MODEL_ad71bc9d32cb4684af8498f125a65bdf", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "39162785d9954803a67e4053ec694ad9": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "40d26c433bd2492a89755d08a1214a27": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "40f2f5c8dcf34b8eb272fcb0680dfa00": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "cell_id": "", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_b70b532c7b0942f792415fc6c0fe5fa7", - "layout_path": null, - "placeholder": "​", - "position": "inline", - "render_slot_id": null, - "source_cell_id": null, - "style": "IPY_MODEL_78e6ef2a955749668cd65184d203e3b9", - "tabbable": null, - "tooltip": null, - "value": "
CX Discovery — topics
8 topics · ~110 minutes
0 of 8 topics complete
Background & Organizationdiscussing now →Not started
Lines of business, channels, org structure, decision-making
  • Lines of business & scope
  • Channels supported
  • Org & reporting structure
  • Decision-making & priorities
  • Problems & causes today
CX StrategyNot started
How CX value is defined, measured, and organized to deliver
ChannelsNot started
Inbound & outbound volumes, metrics, contact reasons
Agent & Supervisor EnvironmentNot started
Locations, endpoints, desktop, applications, knowledge
Routing & AutomationNot started
Self-service, IVR, speech, virtual agents, agent assist, RPA
Workforce EngagementNot started
Call recording / QM, WFM, agent self-service, integrations
TrainingNot started
Onboarding, format, assessment, eLearning, QM integration
Reporting & InsightsNot started
Real-time & historical reporting, analytics, BI, CRM
" - } - }, - "4577562ceab14ac59502bf3d61b3558b": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization: Lines of business & scope", - "last_changed_at": "", - "layout": "IPY_MODEL_bad488e019f4405ba4b6e26fb839b045", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "45a62567adda4b4aabaf537745a299f7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4bf057077ef54111826b01e09cb0c2d7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4ed8a64da5d043d49311e8adec77d074": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "51e582f774904655b799a688e7297095": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "52d9e6ee9d2746e69d60cc3af8f5b01d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "59d4a779c9eb46a69d092e98bcd5bc3e": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Routing & Automation: Virtual agents & assist", - "last_changed_at": "", - "layout": "IPY_MODEL_51e582f774904655b799a688e7297095", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "5a26381ab5a546edb671524ffec3093d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5b191db98ba842e59c70203d4442805b": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5d3978523d1a4831ad357ea51fc29500": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5ea3a44bff7c4ce4a63937abc5abf078": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "65eecaf612d54035945e88ac4e13134b": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6a6c72a002a14996806931189dc2a762": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6a9fc0b7b44a4432b204046b1f105dd1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6c1b7167e9b1427b88bc6294a069c64e": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.select.SelectWidget", - "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "choices": [ - "Not started", - "In progress", - "Complete", - "Skipped" - ], - "disabled": false, - "hidden": false, - "label": "Training — status", - "layout": "IPY_MODEL_79777a72dd864de0a53e34d2c8ac3fb4", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "Not started" - } - }, - "6d51502a805c4ad0a84412e31231a8dc": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization: Decision-making & priorities", - "last_changed_at": "", - "layout": "IPY_MODEL_31ce043e86034f2c89ab70982964edba", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "6d8dd95bf76f448ca63954842e0c252d": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Reporting & Insights — notes", - "layout": "IPY_MODEL_8126e4c8e8df478f8efd609ccb119784", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" - } - }, - "70f6f83bca334c7688b05d8a09764cb6": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "CX Strategy — notes", - "layout": "IPY_MODEL_e4b85b0d4e184c4094afa3f4db7454a2", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" - } - }, - "726d90aecdba4272ad0c4fbb809f48ed": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "77fc6178e65e4d7ca76c8adb00f2c115": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Channels: Digital channels", - "last_changed_at": "", - "layout": "IPY_MODEL_40d26c433bd2492a89755d08a1214a27", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "78e6ef2a955749668cd65184d203e3b9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "79777a72dd864de0a53e34d2c8ac3fb4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "7f80da0508a4497688d605d037802248": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "7fc16187077848d59babbd224d3ceb4c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "7fec23b1406644df93028629ae246e38": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8126e4c8e8df478f8efd609ccb119784": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8193b4d5cb2b4f32853bc9c057f3af1e": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Channels: Inbound contact reasons", - "last_changed_at": "", - "layout": "IPY_MODEL_5d3978523d1a4831ad357ea51fc29500", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "8368207a3d684c9b955efb9922b8e7b9": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.select.SelectWidget", - "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "choices": [ - "Not started", - "In progress", - "Complete", - "Skipped" - ], - "disabled": false, - "hidden": false, - "label": "Agent & Supervisor Environment — status", - "layout": "IPY_MODEL_8ad428b804264804912f8a352f1d6eb6", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "Not started" - } - }, - "85f77e3f4ac341ea8465de7ed8e60362": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8633016a74bf45edbcab256647887d2e": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8686a07e5bd840f181b8fb1295280bc0": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Workforce Engagement: Workforce management", - "last_changed_at": "", - "layout": "IPY_MODEL_c03be02e32da4d1590130e25e7a660da", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "8ad428b804264804912f8a352f1d6eb6": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "96e8b19e7f334299a6b55590bb9161b2": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization: Org & reporting structure", - "last_changed_at": "", - "layout": "IPY_MODEL_7fec23b1406644df93028629ae246e38", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "a451eea831e647c397da7b1450a4a92f": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.select.SelectWidget", - "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "choices": [ - "Not started", - "In progress", - "Complete", - "Skipped" - ], - "disabled": false, - "hidden": false, - "label": "Background & Organization — status", - "layout": "IPY_MODEL_65eecaf612d54035945e88ac4e13134b", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "Not started" - } - }, - "a8206b8bd1694936af419af888e57e84": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Routing & Automation: Self-service & IVR", - "last_changed_at": "", - "layout": "IPY_MODEL_8633016a74bf45edbcab256647887d2e", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "ad71bc9d32cb4684af8498f125a65bdf": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b3115751d9fa4beabb2f152ed21a0ae3": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Channels — notes", - "layout": "IPY_MODEL_2fbc83509c604715ba49f524282af285", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" - } - }, - "b48d623d4ba04ed29b206f4e1cf7c40b": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b70b532c7b0942f792415fc6c0fe5fa7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b72bd8acfd374276b0ebdd203075c9b6": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Routing & Automation — notes", - "layout": "IPY_MODEL_b96a9d0c486b47699811834fae47a695", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" - } - }, - "b96a9d0c486b47699811834fae47a695": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "bad488e019f4405ba4b6e26fb839b045": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "bdad679e97ff4eb3ad07cbe4a8643a58": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c03be02e32da4d1590130e25e7a660da": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c20bedce4b4b4854a6c14bafc87295fc": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c7e7ba21fe704e618b6244464034b9d9": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "ca66db1b382a49d4af0b4ce9593e6e70": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Reporting & Insights: Reporting", - "last_changed_at": "", - "layout": "IPY_MODEL_4ed8a64da5d043d49311e8adec77d074", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "d04723c27a694c48965d347729954375": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization: Channels supported", - "last_changed_at": "", - "layout": "IPY_MODEL_7f80da0508a4497688d605d037802248", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "d1022625168744a897229cff827f8392": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization: Problems & causes today", - "last_changed_at": "", - "layout": "IPY_MODEL_1491ef06c9ea4b15b3c72de83b38a374", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "d5d6f1aa4fbd409699530cc1bca9b74d": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Background & Organization — notes", - "layout": "IPY_MODEL_f78c1d8dc95f44c09e4aa7e559750a51", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" - } - }, - "d6fef173bf9e42179464b5034b9728e9": { + "50c76b29ed32468badebe6ff4f9f1bb0": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -5090,7 +3229,7 @@ "disabled": false, "hidden": false, "label": "Workforce Engagement — status", - "layout": "IPY_MODEL_e1b875aa9a4348a18fe8a5d3412c5e12", + "layout": "IPY_MODEL_cb3fcd9d52cd43f6a1ed9b2e2e4fd065", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -5101,7 +3240,609 @@ "value": "Not started" } }, - "d980c0367d404f7e9397b9b37021ffbd": { + "50deb959f8ae48f88ce5e485a4131fbe": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "53587648b3aa431682d06e4dbe401f8d": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Workforce Engagement: Workforce management", + "last_changed_at": "", + "layout": "IPY_MODEL_96a376557a5449c884051ab62d2a28f0", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "579c991732914cb6b24d468ba77d1d5b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5d4d52998a594074bd935b1a8105eed9": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Reporting & Insights: Reporting", + "last_changed_at": "", + "layout": "IPY_MODEL_7f52f7fd05a14a50a281b9c5ec5b5fd2", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "6304c720969d412f97d54a7a2b1d2360": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "63ff21a9bdb649cc9d2e4b7d58baffec": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Agent & Supervisor Environment: Locations & endpoints", + "last_changed_at": "", + "layout": "IPY_MODEL_a6f992632c2d4739916ddf5c15b7a0f0", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "6a3c639539274c93913e11eaba5528b6": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Training — notes", + "layout": "IPY_MODEL_e98c64895e4b40439efca1c8e576a35d", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } + }, + "6a7a2a1d35e74fecbed1a2fb4d1f61e4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6eb6e4b2535949e6bdc2ef86f27fa7d5": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization: Channels supported", + "last_changed_at": "", + "layout": "IPY_MODEL_b73bf6655a994e7fafe9144894ee140d", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "70026e6c871a4a6295b2999e512e77e8": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Routing & Automation: Self-service & IVR", + "last_changed_at": "", + "layout": "IPY_MODEL_8677632009224150b15b4611b3857650", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "7302bae9ac134fe891f312ef56eaffd9": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization: Decision-making & priorities", + "last_changed_at": "", + "layout": "IPY_MODEL_82f1f81bb41a4ea39f2a7882b70d2e30", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "74c803b80eb749348f9215c7f0bf1ae9": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization: Org & reporting structure", + "last_changed_at": "", + "layout": "IPY_MODEL_f766085686be42d4aa2c2c99427b1f16", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "75964ed9b3bc41b392f597d3d1de7732": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "CX Strategy: Organization & operating model", + "last_changed_at": "", + "layout": "IPY_MODEL_adda56336c1649e6a9eb361c9be70fff", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "77865f023ab94b3784bf0bfb35eec22b": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Routing & Automation: Virtual agents & assist", + "last_changed_at": "", + "layout": "IPY_MODEL_10bcd8e392d44ba787e16984dd019565", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "7c10ad36a20944459ce75e41397e74be": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7c46a99022614830981147c007b7b82a": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -5127,7 +3868,7 @@ "disabled": false, "hidden": false, "label": "Reporting & Insights — status", - "layout": "IPY_MODEL_c20bedce4b4b4854a6c14bafc87295fc", + "layout": "IPY_MODEL_46e8479ff738471fb4fce78dc41384c0", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -5138,75 +3879,7 @@ "value": "Not started" } }, - "dafa8c55991946a187fe9d550de53414": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "CX Strategy: Continuous improvement", - "last_changed_at": "", - "layout": "IPY_MODEL_c7e7ba21fe704e618b6244464034b9d9", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "dfdc4e514e694492a37dcc756e7dc5b7": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", - "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", - "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Channels: Inbound context", - "last_changed_at": "", - "layout": "IPY_MODEL_194b98c3b1ab4c828036e196974cbbfa", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false - } - }, - "e1b875aa9a4348a18fe8a5d3412c5e12": { + "7cf7689da95d411bbe31b4db1c2d358f": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -5259,7 +3932,41 @@ "width": null } }, - "e4b85b0d4e184c4094afa3f4db7454a2": { + "7ed6ace9dd0c4cd5add9b62128ad94ae": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "CX Strategy: Employee engagement", + "last_changed_at": "", + "layout": "IPY_MODEL_d534c8937793492a843efeae1e1676d0", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "7f52f7fd05a14a50a281b9c5ec5b5fd2": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -5312,174 +4019,272 @@ "width": null } }, - "ea3fc8677a1c4aec8eca4f0bc1f7a5a8": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", + "80fca3f575234c3ba087ab24a4e0a1b1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", "state": { - "_anywidget_id": "mercury.text.TextInputWidget", - "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Workforce Engagement — notes", - "layout": "IPY_MODEL_7fc16187077848d59babbd224d3ceb4c", - "layout_path": null, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": "" + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null } }, - "ee709981f4454a96b98a9c9bb1e6d4ca": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", + "82f1f81bb41a4ea39f2a7882b70d2e30": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Training: Onboarding & enablement", - "last_changed_at": "", - "layout": "IPY_MODEL_726d90aecdba4272ad0c4fbb809f48ed", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null } }, - "f0aabb7f6d944dfa839728a33422d5c8": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", + "8319aeb5add740b0b1949c4cbb34bae7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Workforce Engagement: Agent self-service & integrations", - "last_changed_at": "", - "layout": "IPY_MODEL_b48d623d4ba04ed29b206f4e1cf7c40b", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null } }, - "f0d9c25925fc45a8b1005234f675ace8": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", + "8677632009224150b15b4611b3857650": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Agent & Supervisor Environment: Desktop & applications", - "last_changed_at": "", - "layout": "IPY_MODEL_45a62567adda4b4aabaf537745a299f7", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null } }, - "f0fae9e3698e4246ab05f98f02919d30": { - "model_module": "anywidget", - "model_module_version": "~0.11.*", - "model_name": "AnyModel", + "8922090ab38b4984be10659d45650f2e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", "state": { - "_anywidget_id": "mercury.checkbox.CheckboxWidget", - "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", - "_dom_classes": [], - "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", - "_model_module": "anywidget", - "_model_module_version": "~0.11.*", - "_model_name": "AnyModel", + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", "_view_count": null, - "_view_module": "anywidget", - "_view_module_version": "~0.11.*", - "_view_name": "AnyView", - "appearance": "box", - "cell_id": "", - "disabled": false, - "hidden": false, - "label": "Workforce Engagement: Call recording & Quality Management", - "last_changed_at": "", - "layout": "IPY_MODEL_6a9fc0b7b44a4432b204046b1f105dd1", - "layout_path": null, - "n_toggles": 0, - "position": "sidebar", - "render_slot_id": null, - "source_cell_id": null, - "tabbable": null, - "tooltip": null, - "url_key": "", - "value": false + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null } }, - "f31201923cb44e409e1ea78501f97681": { + "8f091c414a234436990cbabb0d247de9": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -5505,7 +4310,7 @@ "disabled": false, "hidden": false, "label": "CX Strategy — status", - "layout": "IPY_MODEL_ff74b3278d63442db94751da76210d83", + "layout": "IPY_MODEL_33591c514cf243208a28c314bf94830b", "layout_path": null, "position": "sidebar", "render_slot_id": null, @@ -5516,7 +4321,499 @@ "value": "Not started" } }, - "f669b4e9c5164f8fb734f4f3060e19ea": { + "8f60dbda5b484579940066e5b0fd26ee": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "96a376557a5449c884051ab62d2a28f0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a1654c12728e4d2f876a557dbf9da8c1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a250beba66ae4e149f0daa8c353627c7": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Routing & Automation — notes", + "layout": "IPY_MODEL_43656bba585e4b8c86168b6eeaea3530", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } + }, + "a3819a7c608e439d970efe9652597785": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a6520b5d0ea3476da951c3f026ef46ee": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.select.SelectWidget", + "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "choices": [ + "Not started", + "In progress", + "Complete", + "Skipped" + ], + "disabled": false, + "hidden": false, + "label": "Background & Organization — status", + "layout": "IPY_MODEL_de23955c5f1e4ba9b1d793f97035e0ea", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "Not started" + } + }, + "a6f992632c2d4739916ddf5c15b7a0f0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a9d54509526c4ed59e7951e0f1a9e461": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "aa0972e2e60b481d8f7746be441641e0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "adda56336c1649e6a9eb361c9be70fff": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "af6a4133d4f5443d9906ec19d6a5a322": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -5536,9 +4833,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "CX Strategy: Insight & Voice of Customer", + "label": "CX Strategy: Continuous improvement", "last_changed_at": "", - "layout": "IPY_MODEL_2e29bd2928674e31b3fd1e2d485a29ed", + "layout": "IPY_MODEL_a9d54509526c4ed59e7951e0f1a9e461", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -5550,113 +4847,25 @@ "value": false } }, - "f756ed14bebb4b1787445e09b080dd1d": { - "model_module": "@jupyter-widgets/base", + "b1181ef7440a47a7916b4367d2e912aa": { + "model_module": "@jupyter-widgets/controls", "model_module_version": "2.0.0", - "model_name": "LayoutModel", + "model_name": "HTMLStyleModel", "state": { - "_model_module": "@jupyter-widgets/base", + "_model_module": "@jupyter-widgets/controls", "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", + "_model_name": "HTMLStyleModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null } }, - "f78c1d8dc95f44c09e4aa7e559750a51": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "fc04710798de44fb99bc55b34300c448": { + "b1d02643012c4444b6cf899feb8d1fef": { "model_module": "anywidget", "model_module_version": "~0.11.*", "model_name": "AnyModel", @@ -5676,9 +4885,9 @@ "cell_id": "", "disabled": false, "hidden": false, - "label": "Agent & Supervisor Environment: Locations & endpoints", + "label": "Channels: Inbound contact reasons", "last_changed_at": "", - "layout": "IPY_MODEL_bdad679e97ff4eb3ad07cbe4a8643a58", + "layout": "IPY_MODEL_42d5286c049140cba0bbcd5ffbba2359", "layout_path": null, "n_toggles": 0, "position": "sidebar", @@ -5690,7 +4899,44 @@ "value": false } }, - "ff74b3278d63442db94751da76210d83": { + "b568a0ea3a574d71b72e8095ed2b92e2": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.select.SelectWidget", + "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "choices": [ + "Not started", + "In progress", + "Complete", + "Skipped" + ], + "disabled": false, + "hidden": false, + "label": "Routing & Automation — status", + "layout": "IPY_MODEL_041455db212e45ceb2c7b7b29d504825", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "Not started" + } + }, + "b73bf6655a994e7fafe9144894ee140d": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", @@ -5742,6 +4988,842 @@ "visibility": null, "width": null } + }, + "bc46a9479dd14a5b8e57dc6b35afb458": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c225acc8664d4dd482792cc9ad451178": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Channels: Voice & video metrics", + "last_changed_at": "", + "layout": "IPY_MODEL_1af6c54cb9024baba39d308f1a95480b", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "c531a01e2b5445a685aa9de95bd82d51": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization: Problems & causes today", + "last_changed_at": "", + "layout": "IPY_MODEL_7c10ad36a20944459ce75e41397e74be", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "c5b373ec5b224d76b5881b2da681859d": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Routing & Automation: Digital assistant apps", + "last_changed_at": "", + "layout": "IPY_MODEL_6a7a2a1d35e74fecbed1a2fb4d1f61e4", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "cb3fcd9d52cd43f6a1ed9b2e2e4fd065": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ce19eea1122b461e919dd45db8fbbc0a": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.select.SelectWidget", + "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "choices": [ + "Not started", + "In progress", + "Complete", + "Skipped" + ], + "disabled": false, + "hidden": false, + "label": "Training — status", + "layout": "IPY_MODEL_e4247da2f7c54293b7bfeb528d81adc2", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "Not started" + } + }, + "d346a4b5ed514c019df7d18e505233fb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d534c8937793492a843efeae1e1676d0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dd2e189ff03d4b76bd92e483dbe721ae": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "de23955c5f1e4ba9b1d793f97035e0ea": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e1c00f930b2f4fc6bd6e694b1560fe5d": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Channels: Digital channels", + "last_changed_at": "", + "layout": "IPY_MODEL_d346a4b5ed514c019df7d18e505233fb", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "e4247da2f7c54293b7bfeb528d81adc2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e611c2d7646447f29b4d9d4b5bf5fa73": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.select.SelectWidget", + "_css": "\n .mljar-select-container {\n position: relative;\n display: flex;\n flex-direction: column;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n overflow: visible;\n }\n\n .mljar-select-label {\n padding-top: 6px;\n margin-bottom: 4px;\n font-weight: 600;\n }\n\n .mljar-select-control {\n position: relative;\n display: flex;\n align-items: center;\n cursor: default;\n overflow: visible;\n }\n\n .mljar-select-container.is-open {\n z-index: 20;\n }\n\n .mljar-select-widget-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 36px 9px 10px;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-sizing: border-box;\n line-height: 1.4;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n appearance: none !important;\n background-color: #ffffff !important;\n color: #0f172a !important;\n cursor: default;\n }\n\n .mljar-select-widget-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n cursor: text;\n }\n\n .mljar-select-caret {\n position: absolute;\n right: 12px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 1.5px solid #0f172a;\n border-bottom: 1.5px solid #0f172a;\n transform: translateY(-65%) rotate(45deg);\n pointer-events: auto;\n opacity: 0.5;\n transition: transform 0.18s ease, opacity 0.18s ease;\n }\n\n .mljar-select-container.is-open .mljar-select-caret {\n opacity: 1;\n transform: translateY(-35%) rotate(225deg);\n }\n\n .mljar-select-dropdown {\n display: none;\n position: fixed;\n z-index: 10000;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n overflow: hidden;\n }\n\n .mljar-select-list {\n max-height: 260px;\n overflow-y: auto;\n }\n\n .mljar-select-option {\n display: block;\n width: 100%;\n padding: 9px 10px;\n border: 0;\n background: transparent;\n color: #0f172a;\n text-align: left;\n cursor: pointer;\n font: inherit;\n transition: background-color 0.14s ease, color 0.14s ease;\n }\n\n .mljar-select-option:hover {\n background: #f3f3f4;\n }\n\n .mljar-select-option:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-option.is-selected {\n background: #e6f2ff;\n color: #007bff;\n font-weight: 600;\n }\n\n .mljar-select-option.is-selected:hover,\n .mljar-select-option.is-selected:active {\n background: #e6f2ff;\n color: #007bff;\n }\n\n .mljar-select-empty {\n display: none;\n padding: 10px;\n color: #616673;\n font-size: 0.95em;\n }\n\n .mljar-select-widget-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n .mljar-select-control.is-disabled .mljar-select-caret {\n opacity: 0.45;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const normalize = value => String(value ?? \"\").toLowerCase().trim();\n const getChoices = () =>\n Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n const isDisabled = () => !!model.get(\"disabled\");\n const isHidden = () => !!model.get(\"hidden\");\n\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-select-container\");\n\n if (model.get(\"label\")) {\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-select-label\");\n topLabel.innerHTML = model.get(\"label\");\n container.appendChild(topLabel);\n }\n\n const control = document.createElement(\"div\");\n control.classList.add(\"mljar-select-control\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-select-widget-input\");\n input.autocomplete = \"off\";\n input.spellcheck = false;\n\n const caret = document.createElement(\"div\");\n caret.classList.add(\"mljar-select-caret\");\n\n control.appendChild(input);\n control.appendChild(caret);\n\n const dropdown = document.createElement(\"div\");\n dropdown.classList.add(\"mljar-select-dropdown\");\n\n const list = document.createElement(\"div\");\n list.classList.add(\"mljar-select-list\");\n\n const emptyState = document.createElement(\"div\");\n emptyState.classList.add(\"mljar-select-empty\");\n emptyState.textContent = \"No matches\";\n\n dropdown.appendChild(list);\n dropdown.appendChild(emptyState);\n\n container.appendChild(control);\n el.appendChild(container);\n\n let isOpen = false;\n let filteredChoices = [];\n let lastCommittedValue = \"\";\n let isEditing = false;\n document.body.appendChild(dropdown);\n\n const updateDropdownPosition = () => {\n if (!isOpen) {\n return;\n }\n const rect = control.getBoundingClientRect();\n dropdown.style.top = `${rect.bottom + 6}px`;\n dropdown.style.left = `${rect.left}px`;\n dropdown.style.width = `${rect.width}px`;\n };\n\n const setOpen = next => {\n if (isDisabled()) {\n isOpen = false;\n } else {\n isOpen = !!next;\n }\n container.classList.toggle(\"is-open\", isOpen);\n dropdown.style.display = isOpen ? \"block\" : \"none\";\n if (isOpen) {\n updateDropdownPosition();\n }\n };\n\n const updateDisabledState = () => {\n const disabled = isDisabled();\n input.disabled = disabled;\n control.classList.toggle(\"is-disabled\", disabled);\n };\n\n const updateHiddenState = () => {\n container.style.display = isHidden() ? \"none\" : \"\";\n };\n\n const syncInputWithValue = () => {\n const value = model.get(\"value\") || \"\";\n lastCommittedValue = value;\n if (!isEditing) {\n input.value = value;\n }\n };\n\n const filterChoices = query => {\n const normalizedQuery = normalize(query);\n const allChoices = getChoices();\n if (!normalizedQuery) {\n return allChoices;\n }\n return allChoices.filter(choice =>\n normalize(choice).includes(normalizedQuery)\n );\n };\n\n const renderList = () => {\n list.innerHTML = \"\";\n filteredChoices.forEach(choice => {\n const option = document.createElement(\"button\");\n option.type = \"button\";\n option.classList.add(\"mljar-select-option\");\n if (choice === model.get(\"value\")) {\n option.classList.add(\"is-selected\");\n }\n option.textContent = choice;\n option.addEventListener(\"mousedown\", event => {\n event.preventDefault();\n event.stopPropagation();\n model.set(\"value\", choice);\n model.save_changes();\n isEditing = false;\n syncInputWithValue();\n renderList();\n setOpen(false);\n });\n list.appendChild(option);\n });\n\n const hasMatches = filteredChoices.length > 0;\n list.style.display = hasMatches ? \"block\" : \"none\";\n emptyState.style.display = hasMatches ? \"none\" : \"block\";\n };\n\n const refreshList = () => {\n filteredChoices = filterChoices(input.value);\n renderList();\n };\n\n const openWithCurrentQuery = () => {\n isEditing = true;\n input.value = \"\";\n refreshList();\n setOpen(true);\n };\n\n const closeDropdown = () => {\n isEditing = false;\n setOpen(false);\n input.value = lastCommittedValue;\n };\n\n control.addEventListener(\"click\", event => {\n event.stopPropagation();\n if (isDisabled()) {\n return;\n }\n if (event.target === caret && isOpen) {\n closeDropdown();\n input.blur();\n return;\n }\n openWithCurrentQuery();\n input.focus();\n });\n\n input.addEventListener(\"input\", () => {\n if (isDisabled()) {\n return;\n }\n refreshList();\n setOpen(true);\n });\n\n input.addEventListener(\"focus\", () => {\n if (isDisabled()) {\n return;\n }\n openWithCurrentQuery();\n });\n\n input.addEventListener(\"blur\", () => {\n isEditing = false;\n input.value = lastCommittedValue;\n });\n\n const handleDocumentClick = event => {\n if (!container.contains(event.target) && !dropdown.contains(event.target)) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"click\", handleDocumentClick);\n window.addEventListener(\"resize\", updateDropdownPosition);\n document.addEventListener(\"scroll\", updateDropdownPosition, true);\n\n model.on(\"change:value\", () => {\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:choices\", () => {\n const choices = getChoices();\n if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n model.set(\"value\", choices[0]);\n model.save_changes();\n return;\n }\n syncInputWithValue();\n refreshList();\n });\n\n model.on(\"change:disabled\", () => {\n updateDisabledState();\n if (isDisabled()) {\n closeDropdown();\n }\n });\n\n model.on(\"change:hidden\", () => {\n updateHiddenState();\n });\n\n updateDisabledState();\n updateHiddenState();\n syncInputWithValue();\n refreshList();\n setOpen(false);\n\n return () => {\n dropdown.remove();\n document.removeEventListener(\"click\", handleDocumentClick);\n window.removeEventListener(\"resize\", updateDropdownPosition);\n document.removeEventListener(\"scroll\", updateDropdownPosition, true);\n };\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "choices": [ + "Not started", + "In progress", + "Complete", + "Skipped" + ], + "disabled": false, + "hidden": false, + "label": "Agent & Supervisor Environment — status", + "layout": "IPY_MODEL_a1654c12728e4d2f876a557dbf9da8c1", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "Not started" + } + }, + "e65e3f4ec0cd4f3b8aef0b06807eb9eb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e98c64895e4b40439efca1c8e576a35d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "eeb51c493f684fd4b4469c9bc27f7bef": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.checkbox.CheckboxWidget", + "_css": "\n .mljar-checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n color: #0f172a;\n padding-left: 5px;\n }\n .mljar-checkbox-container.is-disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .mljar-checkbox-input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n box-shadow: inset 0 0 0 2px #007bff;\n }\n\n .mljar-checkbox-label {\n font-size: 14px;\n line-height: 1.2;\n padding-top: 2px;\n }\n\n /* --- Toggle style (15% smaller) --- */\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n position: relative;\n width: 34px;\n height: 19px;\n border-radius: 999px;\n background: #f1f1f2;\n border: 1px solid #cfd1d5;\n transition: background 150ms ease, border-color 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n background: #007bff;\n border-color: #007bff;\n }\n .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 15px;\n height: 15px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n transition: transform 150ms ease;\n }\n .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n transform: translateX(15px);\n }\n\n /* --- Classic box style --- */\n .mljar-checkbox-container.is-box .mljar-checkbox-control {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1px solid #cfd1d5;\n background: #ffffff;\n display: inline-block;\n position: relative;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n border-color: #007bff;\n background: #007bff;\n }\n .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 0px;\n width: 5px;\n height: 10px;\n border: solid #fff;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n\n .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n border-color: #007bff;\n background: #f3f3f4;\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"label\");\n container.classList.add(\"mljar-checkbox-container\");\n\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.classList.add(\"mljar-checkbox-input\");\n\n const control = document.createElement(\"span\");\n control.classList.add(\"mljar-checkbox-control\");\n\n const text = document.createElement(\"span\");\n text.classList.add(\"mljar-checkbox-label\");\n\n container.appendChild(input);\n container.appendChild(control);\n container.appendChild(text);\n el.appendChild(container);\n\n function syncFromModel() {\n // appearance\n const a = model.get(\"appearance\") || \"toggle\";\n container.classList.remove(\"is-toggle\",\"is-box\");\n container.classList.add(`is-${a}`);\n input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n // value\n const v = !!model.get(\"value\");\n if (input.checked !== v) {\n input.checked = v;\n input.setAttribute(\"aria-checked\", String(v));\n }\n container.classList.toggle(\"is-checked\", v);\n\n // disabled\n const d = !!model.get(\"disabled\");\n input.disabled = d;\n container.classList.toggle(\"is-disabled\", d);\n\n // label\n text.textContent = model.get(\"label\") || \"\";\n\n // hidden (exists but not visible)\n container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n }\n\n input.addEventListener(\"change\", () => {\n if (model.get(\"disabled\")) return;\n\n const v = input.checked;\n if (model.get(\"value\") !== v) {\n model.set(\"value\", v);\n model.set(\"last_changed_at\", new Date().toISOString());\n model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n model.save_changes();\n // model.send({ type: \"changed\", value: v });\n }\n });\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:appearance\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n // ---- read cell id (no DOM modifications) ----\n /*\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n \n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "appearance": "box", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Channels: Outbound", + "last_changed_at": "", + "layout": "IPY_MODEL_174a2effb95041b0b61ca80bf0d5c226", + "layout_path": null, + "n_toggles": 0, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": false + } + }, + "f124bb0f618046d59879ddfe613b2d57": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "CX Strategy — notes", + "layout": "IPY_MODEL_579c991732914cb6b24d468ba77d1d5b", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } + }, + "f766085686be42d4aa2c2c99427b1f16": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fc9718f4007145fcb6382bb421ab30e4": { + "model_module": "anywidget", + "model_module_version": "~0.11.*", + "model_name": "AnyModel", + "state": { + "_anywidget_id": "mercury.text.TextInputWidget", + "_css": "\n .mljar-textinput-container {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n width: 100%;\n min-width: 120px;\n font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: normal;\n color: #0f172a;\n padding-left: 4px;\n padding-right: 4px;\n box-sizing: border-box;\n }\n\n .mljar-textinput-top-label {\n padding-top: 6px;\n margin-bottom: 6px;\n text-align: left;\n width: 100%;\n font-weight: 600;\n }\n\n .mljar-textinput-input {\n width: 100%;\n min-height: 40px;\n padding: 9px 10px;\n box-sizing: border-box;\n border: 1px solid #cfd1d5;\n border-radius: 6px;\n background: #ffffff;\n color: #0f172a;\n line-height: 1.4;\n\n appearance: none !important;\n background-color: #ffffff !important;\n }\n\n .mljar-textinput-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: none;\n }\n\n .mljar-textinput-input:disabled {\n background: #f5f5f5;\n color: #888;\n cursor: not-allowed;\n }\n\n @media (max-width: 768px) {\n .mljar-textinput-input {\n min-height: 44px;\n padding: 10px 12px;\n }\n }\n ", + "_dom_classes": [], + "_esm": "\n function render({ model, el }) {\n const container = document.createElement(\"div\");\n container.classList.add(\"mljar-textinput-container\");\n\n const topLabel = document.createElement(\"div\");\n topLabel.classList.add(\"mljar-textinput-top-label\");\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.classList.add(\"mljar-textinput-input\");\n\n container.appendChild(topLabel);\n container.appendChild(input);\n el.appendChild(container);\n\n let debounceTimer = null;\n\n input.addEventListener(\"input\", () => {\n if (model.get(\"disabled\")) return;\n model.set(\"value\", input.value);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => model.save_changes(), 200);\n });\n\n function syncFromModel() {\n topLabel.innerHTML = model.get(\"label\") || \"Enter text\";\n input.value = model.get(\"value\") ?? \"\";\n\n const disabled = !!model.get(\"disabled\");\n input.disabled = disabled;\n\n const hidden = !!model.get(\"hidden\");\n container.style.display = hidden ? \"none\" : \"flex\";\n }\n\n model.on(\"change:value\", syncFromModel);\n model.on(\"change:label\", syncFromModel);\n model.on(\"change:disabled\", syncFromModel);\n model.on(\"change:hidden\", syncFromModel);\n\n syncFromModel();\n\n /*\n // ---- read cell id (no DOM modifications) ----\n const ID_ATTR = \"data-cell-id\";\n const hostWithId = el.closest(`[${ID_ATTR}]`);\n const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n if (cellId) {\n model.set(\"cell_id\", cellId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: cellId });\n } else {\n const mo = new MutationObserver(() => {\n const host = el.closest(`[${ID_ATTR}]`);\n const newId = host?.getAttribute(ID_ATTR);\n if (newId) {\n model.set(\"cell_id\", newId);\n model.save_changes();\n model.send({ type: \"cell_id_detected\", value: newId });\n mo.disconnect();\n }\n });\n mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n }\n */\n }\n export default { render };\n ", + "_model_module": "anywidget", + "_model_module_version": "~0.11.*", + "_model_name": "AnyModel", + "_view_count": null, + "_view_module": "anywidget", + "_view_module_version": "~0.11.*", + "_view_name": "AnyView", + "cell_id": "", + "disabled": false, + "hidden": false, + "label": "Background & Organization — notes", + "layout": "IPY_MODEL_aa0972e2e60b481d8f7746be441641e0", + "layout_path": null, + "position": "sidebar", + "render_slot_id": null, + "source_cell_id": null, + "tabbable": null, + "tooltip": null, + "url_key": "", + "value": "" + } } }, "version_major": 2, diff --git a/studies/202607_CX_Discovery_Workshop/pyproject.toml b/assessments/CX_Discovery_Workshop/pyproject.toml similarity index 98% rename from studies/202607_CX_Discovery_Workshop/pyproject.toml rename to assessments/CX_Discovery_Workshop/pyproject.toml index 5cfb702..afa3ebe 100644 --- a/studies/202607_CX_Discovery_Workshop/pyproject.toml +++ b/assessments/CX_Discovery_Workshop/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "discoverylib" -version = "0.1.0" +version = "0.3.0" description = "CX Exploration & Discovery Workshop — live facilitation aid (Mercury Notebook Pattern)" requires-python = ">=3.10" # The notebook is the deliverable (served with Mercury, exported via diff --git a/assessments/CX_Discovery_Workshop/scripts/export_report.py b/assessments/CX_Discovery_Workshop/scripts/export_report.py new file mode 100644 index 0000000..3e2b4bc --- /dev/null +++ b/assessments/CX_Discovery_Workshop/scripts/export_report.py @@ -0,0 +1,123 @@ +"""Export the discovery notebook as an LLM-readable report source. + +Executes the notebook ONCE (widget defaults — or whatever you've captured +and saved in the notebook), then converts the executed copy twice: + + exports/cx_discovery.html — human-reviewable, full presentation + exports/cx_discovery.md — leanest LLM input: presentation-tagged cells + (setup, widgets, board) are stripped, a + framing preamble is prepended, and the data + appendix ends the file with one fenced JSON + block — the machine source of truth. + +Engagement identity (client, date, facilitator) is read from the notebook's +``engagement-data`` cell and stamped into the preamble. + +Run from the study root: python scripts/export_report.py +""" +from __future__ import annotations + +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parent.parent +NOTEBOOK = ROOT / "notebooks" / "cx_discovery.ipynb" +EXPORTS = ROOT / "exports" + +# Cells tagged with any of these never reach the .md export — they are +# stage presentation (source and widget-repr noise), not session record. +STRIP_TAGS_FROM_MD = '{"presentation"}' + + +def engagement_data() -> dict[str, Any]: + """Exec the engagement-data cell (same trick as tests/conftest.py).""" + import nbformat + + nb = nbformat.read(NOTEBOOK, as_version=4) + cells = [c for c in nb.cells + if "engagement-data" in c.metadata.get("tags", [])] + assert len(cells) == 1, "expected exactly one engagement-data cell" + ns: dict[str, Any] = {} + exec(compile(cells[0].source, f"{NOTEBOOK.name} [engagement-data]", "exec"), ns) + return ns["ENGAGEMENT"] # type: ignore[no-any-return] + + +def preamble() -> str: + eng = engagement_data() + who = " · ".join(str(eng[k]).strip() + for k in ("client", "workshop_date", "facilitator") + if str(eng.get(k, "")).strip()) + attendees = ", ".join(str(a) for a in eng.get("attendees", ()) or ()) + if who: + line = who + (f" — attendees: {attendees}" if attendees else "") + else: + line = "master copy — placeholders; no engagement captured." + return "\n".join([ + "", + "**What this is** — the exported record of a CX Exploration & Discovery", + "workshop session, produced from the Mercury-served notebook that ran the", + "session. It is LLM input for drafting the survey write-up or seeding a", + "business case.", + "", + f"**Engagement** — {line}", + "", + "**How to read it** — first the workshop content as annotated source (the", + "topic bank, then the engagement data), then the facilitation script", + "(every prompt, grouped by topic and sub-topic), the verification gate,", + "and finally the captured-session record: the per-topic table (status,", + "covered sub-topics, notes) and — last — **Session state (JSON)**, one", + "fenced `json` block. Where prose and JSON disagree, the JSON block is", + "the source of truth.", + "", + "---", + "", + "", + ]) + + +def main() -> None: + if len(sys.argv) > 1 and sys.argv[1] not in NOTEBOOK.name: + sys.exit(f"no notebook matches {sys.argv[1]!r}") + EXPORTS.mkdir(exist_ok=True) + + with tempfile.TemporaryDirectory() as tmp: + executed = Path(tmp) / NOTEBOOK.name + # 1. Execute once — both formats convert the same session state. + # (Never combine --execute with TagRemovePreprocessor in one call: + # the cell could be stripped before it runs.) + subprocess.run( + [sys.executable, "-m", "nbconvert", "--execute", + "--to", "notebook", "--output", str(executed), str(NOTEBOOK)], + check=True, cwd=ROOT, + ) + # 2. HTML — full presentation, human review artifact. + subprocess.run( + [sys.executable, "-m", "nbconvert", "--to", "html", + "--output-dir", str(EXPORTS), "--output", NOTEBOOK.stem, + str(executed)], + check=True, cwd=ROOT, + ) + # 3. Markdown — LLM artifact: strip presentation cells. + subprocess.run( + [sys.executable, "-m", "nbconvert", "--to", "markdown", + "--output-dir", str(EXPORTS), "--output", NOTEBOOK.stem, + "--TagRemovePreprocessor.enabled=True", + f"--TagRemovePreprocessor.remove_cell_tags={STRIP_TAGS_FROM_MD}", + str(executed)], + check=True, cwd=ROOT, + ) + + # 4. Prepend the framing preamble to the markdown export. + md = EXPORTS / f"{NOTEBOOK.stem}.md" + md.write_text(preamble() + md.read_text(encoding="utf-8"), encoding="utf-8") + + for p in sorted(EXPORTS.iterdir()): + if p.suffix in (".html", ".md"): + print(f"wrote {p.relative_to(ROOT)} ({p.stat().st_size / 1024:,.0f} KB)") + + +if __name__ == "__main__": + main() diff --git a/studies/202607_CX_Discovery_Workshop/tests/conftest.py b/assessments/CX_Discovery_Workshop/tests/conftest.py similarity index 51% rename from studies/202607_CX_Discovery_Workshop/tests/conftest.py rename to assessments/CX_Discovery_Workshop/tests/conftest.py index 450f401..7fa8a30 100644 --- a/studies/202607_CX_Discovery_Workshop/tests/conftest.py +++ b/assessments/CX_Discovery_Workshop/tests/conftest.py @@ -1,10 +1,10 @@ -"""Test plumbing: import path + the topic bank served from the notebook. +"""Test plumbing: import path + notebook content served from tagged cells. -The bank is CONTENT and lives in the notebook — the cell tagged -``topic-bank`` in ``notebooks/cx_discovery.ipynb``; ``.py`` files hold code -only. The fixtures below read that cell with nbformat and exec it, so -pytest pins the exact content the deliverable ships (no kernel needed — -the cell is self-contained by contract). +Content and client data live in the notebook, never in ``.py`` — the cells +tagged ``topic-bank`` and ``engagement-data`` in +``notebooks/cx_discovery.ipynb``. The fixtures below read those cells with +nbformat and exec them, so pytest pins the exact content the deliverable +ships (no kernel needed — tagged cells are self-contained by contract). The sys.path insert makes discoverylib importable even without the study venv active (the normal setup is ``pip install -e ".[dev]"`` into the @@ -25,22 +25,27 @@ sys.path.insert(0, str(STUDY_ROOT)) NOTEBOOK = STUDY_ROOT / "notebooks" / "cx_discovery.ipynb" -@pytest.fixture(scope="session") -def topic_bank() -> dict[str, Any]: - """The executed namespace of the notebook's topic-bank cell.""" +def tagged_cell_ns(tag: str) -> dict[str, Any]: + """Exec the single cell carrying ``tag`` and return its namespace.""" import nbformat nb = nbformat.read(NOTEBOOK, as_version=4) - cells = [c for c in nb.cells if "topic-bank" in c.metadata.get("tags", [])] + cells = [c for c in nb.cells if tag in c.metadata.get("tags", [])] assert len(cells) == 1, ( - f"expected exactly one cell tagged 'topic-bank' in {NOTEBOOK.name}, " + f"expected exactly one cell tagged {tag!r} in {NOTEBOOK.name}, " f"found {len(cells)}" ) ns: dict[str, Any] = {} - exec(compile(cells[0].source, f"{NOTEBOOK.name} [topic-bank]", "exec"), ns) + exec(compile(cells[0].source, f"{NOTEBOOK.name} [{tag}]", "exec"), ns) return ns +@pytest.fixture(scope="session") +def topic_bank() -> dict[str, Any]: + """The executed namespace of the notebook's topic-bank cell.""" + return tagged_cell_ns("topic-bank") + + @pytest.fixture(scope="session") def topics(topic_bank: dict[str, Any]) -> tuple[Any, ...]: """The TOPICS tuple as the deliverable defines it.""" @@ -50,3 +55,9 @@ def topics(topic_bank: dict[str, Any]) -> tuple[Any, ...]: @pytest.fixture(scope="session") def topic_by_key(topics: tuple[Any, ...]) -> dict[str, Any]: return {t.key: t for t in topics} + + +@pytest.fixture(scope="session") +def engagement() -> dict[str, Any]: + """The ENGAGEMENT dict as the deliverable's engagement-data cell ships it.""" + return tagged_cell_ns("engagement-data")["ENGAGEMENT"] # type: ignore[no-any-return] diff --git a/assessments/CX_Discovery_Workshop/tests/test_engagement.py b/assessments/CX_Discovery_Workshop/tests/test_engagement.py new file mode 100644 index 0000000..2706296 --- /dev/null +++ b/assessments/CX_Discovery_Workshop/tests/test_engagement.py @@ -0,0 +1,44 @@ +"""Engagement-data cell — shape pins for the client-facts cell. + +The cell (tagged ``engagement-data``) carries client-specific session facts +and lives in the notebook, never in ``.py``. The MASTER ships placeholders; +an engagement copy ships real values — both must stay green, so these pins +check SHAPE only and never assert emptiness (or any particular value). +""" + +import json +import pathlib + +from discoverylib import build_session, session_json + +NOTEBOOK = ( + pathlib.Path(__file__).resolve().parent.parent / "notebooks" / "cx_discovery.ipynb" +) + + +def test_keys_and_types(engagement): + assert set(engagement) == {"client", "workshop_date", "facilitator", "attendees"} + for key in ("client", "workshop_date", "facilitator"): + assert isinstance(engagement[key], str) + assert all(isinstance(a, str) for a in engagement["attendees"]) + + +def test_flows_into_export_meta(topics, engagement): + payload = session_json(build_session(topics, {}), meta=engagement) + assert payload["meta"]["client"] == engagement["client"] + assert payload["assessment"] == "CX_Discovery_Workshop" + json.dumps(payload) # the attendees tuple serializes as a JSON list + + +def test_cell_sits_above_the_widgets(): + # Position rule: engagement data must never re-run on a sidebar change, + # so its cell precedes the widget-defining cell (Mercury re-runs only + # cells BELOW a changed widget's cell). + import nbformat + + nb = nbformat.read(NOTEBOOK, as_version=4) + eng = next(i for i, c in enumerate(nb.cells) + if "engagement-data" in c.metadata.get("tags", [])) + widgets = next(i for i, c in enumerate(nb.cells) + if c.cell_type == "code" and "mr.Select(" in c.source) + assert eng < widgets, "engagement-data cell must sit above the widget cell" diff --git a/studies/202607_CX_Discovery_Workshop/tests/test_session.py b/assessments/CX_Discovery_Workshop/tests/test_session.py similarity index 92% rename from studies/202607_CX_Discovery_Workshop/tests/test_session.py rename to assessments/CX_Discovery_Workshop/tests/test_session.py index e14595a..d7cd058 100644 --- a/studies/202607_CX_Discovery_Workshop/tests/test_session.py +++ b/assessments/CX_Discovery_Workshop/tests/test_session.py @@ -87,10 +87,14 @@ def test_build_session_shape_and_subtopic_done_count(topics, topic_by_key): assert ch.status == IN_PROGRESS assert ch.subtopics_done == 2 assert ch.subtopics_total == len(topic_by_key["channels"].subtopics) + # WHICH threads were covered, in canonical bank order — the export's + # whole purpose is naming them, not just counting them. + assert ch.subtopics_covered == ("voice_metrics", "outbound") assert ch.notes == "6 channels; voice ~70%" # trimmed bg = next(ts for ts in s["topics"] if ts.key == "background") assert bg.status == COMPLETE and bg.subtopics_done == 0 + assert bg.subtopics_covered == () def test_session_json_is_plain_and_complete(topics): @@ -100,7 +104,7 @@ def test_session_json_is_plain_and_complete(topics): payload = session_json(build_session(topics, status, notes, done), meta={"client": "Acme", "date": "2026-07-19"}) - assert payload["study"] == "202607_CX_Discovery_Workshop" + assert payload["assessment"] == "CX_Discovery_Workshop" assert payload["meta"]["client"] == "Acme" assert payload["progress"]["completed"] == 1 assert payload["progress"]["fraction_complete"] == round(1 / 8, 4) @@ -111,6 +115,7 @@ def test_session_json_is_plain_and_complete(topics): assert bg["notes"] == "3 LOBs; PCI in scope" ch = next(t for t in payload["topics"] if t["key"] == "channels") assert ch["subtopics_done"] == 1 + assert ch["subtopics_covered"] == ["voice_metrics"] # JSON-serializable (no dataclasses / sets leaked through) import json diff --git a/assessments/CX_Discovery_Workshop/tests/test_staging.py b/assessments/CX_Discovery_Workshop/tests/test_staging.py new file mode 100644 index 0000000..a584d20 --- /dev/null +++ b/assessments/CX_Discovery_Workshop/tests/test_staging.py @@ -0,0 +1,28 @@ +"""Stage/backstage detection — Mercury kernels carry MERCURY_CONFIG_DIR.""" + +from discoverylib import staging + + +def test_backstage_prints_only_off_stage(monkeypatch, capsys): + monkeypatch.delenv("MERCURY_CONFIG_DIR", raising=False) + assert not staging.on_stage() + staging.backstage("visible") + assert capsys.readouterr().out == "visible\n" + + monkeypatch.setenv("MERCURY_CONFIG_DIR", "/tmp/app") + assert staging.on_stage() + staging.backstage("hidden") + assert capsys.readouterr().out == "" + + +def test_backstage_md_renders_only_off_stage(monkeypatch, capsys): + # Off stage it must emit SOMETHING (rich markdown under a kernel; + # IPython's display degrades to print under plain pytest) … + monkeypatch.delenv("MERCURY_CONFIG_DIR", raising=False) + staging.backstage_md("**visible**") + assert capsys.readouterr().out != "" + + # … and on stage, nothing at all. + monkeypatch.setenv("MERCURY_CONFIG_DIR", "/tmp/app") + staging.backstage_md("**hidden**") + assert capsys.readouterr().out == "" diff --git a/studies/202607_CX_Discovery_Workshop/tests/test_topics.py b/assessments/CX_Discovery_Workshop/tests/test_topics.py similarity index 100% rename from studies/202607_CX_Discovery_Workshop/tests/test_topics.py rename to assessments/CX_Discovery_Workshop/tests/test_topics.py diff --git a/cxxm.md b/cxxm.md deleted file mode 100644 index 0c15e2c..0000000 --- a/cxxm.md +++ /dev/null @@ -1,453 +0,0 @@ -Contact Center Exploration & Discovery Workshops -The Contact Center Discovery and Exploration workshops allow us to gain a clear perspective of your contact center operations. The discovery workshop is focused on developing an understanding of the current state architecture and IT support. - -I’ve attached a contact center survey to collect information and context in advance of the workshop and is also useful to develop business cases. - -# CX Exploration - -## Background -How many distinct lines of business are supported in the contact centre? - (eg, commercial, residential, retail, wholesale, etc) -Are you using contact center for internal Uses such as help desk, finance, HR, etc.? - -What channels do you support? - Apps - Voice - Video - Chat - SMS - Email - Social Media - Digital Assistants - -Payment card/PCI? - -What are the availability SLAs or targets for your technology platform? - -Mini org chart - -Reporting structure - - Executive leader - -What is the structure of decision making in their organization? Centralized or decentralized? - - Who are the key decision makers for implementing a plan for changes to the contact centre? - -How important is CX & EX to your organization's strategy? - - Is there an executive that is accountable for CX? CXO, CDO? - -Of your leading offers, what is the customer's top priority? - -Competitive pressures? - -Are their pending or recent acquisitions or spinoffs that require changes to their IT infrastructure and services? - -What do they view as the problems in their contact centre today and what are the primary causes? - -CX / Customer Success Leadership: -Are your contact centers managed by the same person? - - Managers: - - Team Leads: - - Agents: - -Who carries the cost of contact centre agents and supervisors? - -What revenue is generated by the contact center? - -Do you have KPI targets for CX?: - CSAT: - NPS: - CES: - How are you doing? - -Is there a VOC program in place? - • ROI/business value known? - • How do customers rate the experience they receive from your organization? 0-5 - - -What are the key features of their corporate culture? - -Is their a Voice of the Agent, or Employee satisfaction survey in place? - - How are they doing? - - -Automation strategy? - -Any inflight projects? - - Any CX or EX improvement initiatives? - -Decision - -Channel management strategy? - -Are your customer experiences personalized? - -Do you have a clear set of design guidelines for CX? - Personas - Journey Mapping - Tools: - -How are processes managed? - -## CX Strategy -How is the value of Customer Experience defined within your organization? - - CX is a key BPI, recognized & measured as a financial value - - - -Strategic Value -What revenue is generated by the contact center? - -What are the key capabilities / services that are delivered by the contact centre? - - - -Proposition -How are you using CX innovation to create market disruption? - -How are competitors using CX to create competitive differentiation? - - -Organization Structure & Operating Model -Is there an executive that is accountable for CX? CXO, CDO? - -Is there a CX team? - Do the CX insights team regularly educate the business - -How are decisions made in the organization, is it centralized or decentralized? - - Who are the key decision makers for investment and changes to the contact centre'? - - Channel management strategy? - - -Who carries the cost of contact centre agents and supervisors? - -Are they managed by the same person? - - -What are the teams? - -How are processes managed? - -Describe your automation strategy. Are there any desires or goals related to the contact centre? - - Who is your leader for Data & AI? - - - -Are there any inflight projects that would impact the contact centre? - -Are there recent or pending acquisitions or spinoffs? - -Insight -How do you use analytics & data to generate a consolidated view of your customer experience? - - -Is there a VOC program in place? - • ROI/business value known? - • How do customers rate the experience they receive from your organization? 0-5 - - -Approach -Continuous Improvement -How is customer insight used to drive CX improvement, loyalty and profitability? -How do you anticipate needs? - -Do you have a clear set of CX design guidelines for CX? - Personas - Journey Mapping - Tools: - -Do you have KPI targets for CX?: - CSAT: - NPS: - CES: - How are you doing? - - -Employee Engagement -Please describe you about your employee / Agent engagement strategy - -How engaged are your people in delivering the customer experience? - -Do you have a VoA/VoE program in place? How are you doing? - -## Channels - -May 6, 2025 -2:52 PM - -Inbound - -Hours of operation - -Who contacts the contact center? (Demographics, their situation) - -Are certain callers or groups prioritized? - -Languages? - English - Canadian French - Spanish - - Video - Average number of active agents: - Voice - Average number of active agents: - Toll Free numbers & DIDs: - Approximate quantities or a list - Hours of operation? Any 24x7? - Average Wait Time: - Average Abandon rate: - Average Handle Time: - Average Call Hold Time: - Average After work call time: - Time to authenticate a caller: - % of calls transferred? - Internally? - Externally? - 3rd parties handling certain types of calls, or being conferenced in? - Courtesy Callback/Virtual Hold - Post Call Survey - First Call Resolution Rate? - Average revenue per call (sales)? - Email, range of logged in users: - ○ Response time: - ○ Email server - Chat, range of logged in users: - Web Chat Internal - Web Chat External - App Integration / embedded - SMS - Facebook Messenger - WhatsApp - Telegram - iMessage - Web Site Forms - Mobile Apps - -Top 3-5 Inbound contact reasons and approximate % of calls: - 1. - -Cost Per call? -Average Handle Time? - -Busiest days: - -Least busy days: - -Seasonal variances? - -What are your most difficult, commonly occurring calls? - -What is the easiest commonly occurring call? - - -Outbound - -Hours of operation - - Voice, range of logged in users: - Preview, volume - Predictive dialer - Email, range of logged in users: - SMS, range of logged in users: - Recorded announcement, ports: - Self service applications - Transfer to agent - Live agent connect - Campaign management - DNC management - -Top 3-5 outbound contact reasons and approximate % of calls: - 1. - - -After call work? - - -## Agent & Supervisor Environment -Location types: - - WFH: - - Offices: - - Hard phone - Soft phone - CODEC? - Headset, wireless?: - Agent greeting or pre-recorded messages - Whisper announcement -PC - Desktop - Laptop - VDI -Agent & Supervisor desktop (omnichannel?): - Custom gadgets - Screen pops - Workflows -Standard browser: -SSO: -Applications used to handle calls: -Knowledge Management: - -## Routing & Automation - Self Service - IVR Persona? - Branding? - Style guides? - Voice actors? - DTMF - Speech - Speech recognition - Text To Speech - Natural Language Understanding - Voice Biometrics - Self service applications: - ID & Validate - Deflection - Situational Offer (Outage/ Time of Day / Scheduled) - API integration: - CRM - Intent Capture - Intent prediction - Offer push based on prediction/account attribute - Virtual Agents - Processes - Agent Assist - RPA - Current challenges or desired capabilities - Process management - Desire to automate - DevOps team? - -Digital Assistant Apps - Alexa - Google Assistant - Siri - - -## Routing & Automation - Self Service - IVR Persona? - Branding? - Style guides? - Voice actors? - DTMF - Speech - Speech recognition - Text To Speech - Natural Language Understanding - Voice Biometrics - Self service applications: - ID & Validate - Deflection - Situational Offer (Outage/ Time of Day / Scheduled) - API integration: - CRM - Intent Capture - Intent prediction - Offer push based on prediction/account attribute - Agent Assist - RPA - Current challenges or desired capabilities - Process management - Desire to automate - DevOps team? - -Digital Assistant Apps - Alexa - Google Assistant - Siri - - -Workforce Engagement - -May 6, 2025 -2:50 PM - -Call Recording/QM - • How is call recording used? - Compliance - Screen capture - Voice transcription - Real time - Desktop analytics - Retention period - Quality Management - Do you have a quality team? - Who do they report to? - How many people work for the team? - Scorecards - Score method and metrics? - How many assessments are conducted? - Coaching - Live Monitor - • Locations with CR - • Number of Named Agents: - - -Workforce Engagement - • What is your recruitment process? - • What qualifications are required for the call center? - • What is the average staff tenure? - • What is the attrition rate? - • What proportion of the staff leave for other positions in the company per annum? - ○ Are staff hired for the CC from other parts of the organization? - • Do you measure agent & supervisor experience? - ○ How are they doing? - ○ What type of recognition program do you have in place? - • Agent adherence measure? - ○ KPIs - ○ Gamification - ○ How long is a shift? - ○ What breaks are allocated - ○ What annualized utilization % do you calculate? - • Forecasting & Scheduling - ○ Peak call volume days, time of month, year? - ○ Validate historical data - ○ How are calculations performed (algorithms used) - ○ WFM Interval (15/30 minutes) - • Intraday/ Realtime adherence - ○ What happens when you are out of compliance, surprises happen? - • Payroll Integration - ○ Payroll platform - • Satisfaction with current tool(s)? - ○ Multiskilled agents - ○ FTE Calculations - • Agent self-service? - ○ How are absences reported? - ○ Shift bids / swaps - ○ Performance metrics - • Number of Named Agents: - • Integration to 3rd party - ○ outsourcer / overflow: - ○ Payroll: ADP / Workday - - -Training - • Onboarding process - ○ How long is it? - ○ Training format? - ○ Assessment? - eLearning - QM Integration - -## Reporting & Insights -Reporting - • Real time - • Historical - • What are the key metrics that you report on? - Data source integration - Dashboards - Wallboards - Agent & Supervisor status - -Analytics & Insights - Do you have an integrated view of customer details and contact history? - CRM: - Does your contact centre collect and use customer insight? - Predictive engagement: - What sort of data analysis do you perform? - Do you have an analytics or business insights team? - How many people? - BI Platform: - What executive level reporting do you perform? - Marketing Team interlock - diff --git a/docs/Assessment_Pattern_V1-00.md b/docs/Assessment_Pattern_V1-00.md new file mode 100644 index 0000000..5ccf1a3 --- /dev/null +++ b/docs/Assessment_Pattern_V1-00.md @@ -0,0 +1,236 @@ +# Assessment Pattern v1.0.0 + +How Palladium ships **Assessments**: reusable workshop instruments (diagnostics, +discovery workshops, maturity assessments) built as Mercury notebook deliverables. +An Assessment is a **master** — client-clean, undated, maintained in this repo — +that becomes a client deliverable only as an **engagement copy** made outside the +repo. Reference implementation: +[`assessments/CX_Discovery_Workshop/`](../assessments/CX_Discovery_Workshop/). + +## 🐾 Red Panda Approval™ + +This pattern follows Red Panda Approval standards (see `CLAUDE.md` for the rubric). + +**Audience note:** written to be loaded whole by an LLM agent building or modifying +an assessment. This document holds what assessments ADD to the +[Mercury Notebook Deliverable Pattern](Mercury_Notebook_Pattern_V1-00.md) — the +mechanics every master obeys (reactivity contract, gate, stage/backstage, packaging, +appendix) live there and are **not restated here**. `CLAUDE.md` is the always-on +contract and takes precedence where documents disagree. + +--- + +## What an Assessment is + +| | Study | **Assessment** | +|---|---|---| +| Reproduces | a dated publication / engagement | — (it IS the instrument) | +| Naming | `YYYYMM_…` (dated) | `Instrument_Name` (undated, living) | +| "Numbers" | dollars (NPV/ROI/payback) | qualitative state (status, scores, coverage) | +| Client data | overlay on a published anchor | the `engagement-data` cell, filled per copy | +| Lifecycle | frozen once delivered | master evolves; copies freeze | + +The **three-layer contract** (from `CLAUDE.md`), which assessments realize most +completely: + +1. **Mercury** — the polished client-facing stage a workshop runs on. +2. **Jupyter cells** — the consultant's surface: workshop **content** and **client + data** are edited in tagged notebook cells, never in `.py`. +3. **Python modules** — the per-assessment engine: schema + session logic, reusable, + typed (`mypy --strict`), and pinned by tests. + +## Master → engagement copy lifecycle + +The master in `assessments/` MUST stay client-clean: placeholder engagement data, +generic content, nothing a client said. Running one for a client means **copying it +out** (checklist below); the copy acquires client data, becomes confidential, and +never merges back. Content improvements discovered on an engagement are hand-carried +back to the master as clean edits. + +--- + +## Anatomy + +``` +assessments// +├── notebooks/.ipynb # THE deliverable — content + data + presentation +├── / # the engine — CODE ONLY, no content +│ ├── session.py # schema, vocabulary, session logic, export payload +│ └── staging.py # stage/backstage (+ backstage_md) — self-contained copy +├── tests/ # content/engagement pins (read from the notebook) + engine pins +├── scripts/export_report.py # execute once → exports/*.html + LLM-ready *.md +├── docs/ # source material (.md/.png only — see .gitignore) +├── exports/ # generated — never committed +├── config.toml # Mercury app shell (brand theme, welcome) +└── pyproject.toml # whole toolchain as core deps (Mercury Pattern §7) +``` + +Masters are **runtime-self-contained**: the engine package (including its own +`staging.py` copy) lives in the master, so an engagement copy runs standalone with no +dependency back on Palladium. Repo-level machinery (the structural test suite, the +review prompt) is *maintenance* tooling — an engagement copy never needs it. + +--- + +## The notebook-first content model + +Everything a consultant edits lives in **tagged cells** of the deliverable notebook. +The tag taxonomy (enforced by `tests/test_notebooks.py` from the repo root): + +| Tag | Count | What it holds | Position rule | +|---|---|---|---| +| `topic-bank` | exactly 1 | the instrument's content: topics, prompts, scoring bands… | above the widgets | +| `engagement-data` | exactly 1 | client facts for THIS session (placeholders in the master) | above the widgets | +| `presentation` | any | setup, widget construction, stage rendering | — | +| `gate` | exactly 1 | the verification gate (Mercury Pattern §4) | after the analytics | +| `data-appendix` | exactly 1 | the machine-readable session record | **last code cell** | + +Rules for content cells (`topic-bank`, `engagement-data`): + +- **Self-contained** — no magics, no shell escapes, imports only from the assessment's + own engine. *Why:* tests and scripts exec the cell by its tag without a kernel; a + `%magic` breaks that contract. +- **Stable keys** — slugs (`key="channels"`) are identities that widgets, captured + notes, and the export JSON key off. NEVER renumber or rename casually; a title edit + renames its sidebar widget label, which resets that widget's state mid-session + (Mercury's widget cache is label-keyed). +- **Verbatim anchor discipline** — when the content structures a source document + (a survey, a question bank), wording tracks the source; the cell's own comment block + carries the editing rules so they travel with the content. +- **Above the widgets** — both content cells sit above the widget cell, so Mercury + never re-runs them on a sidebar change (it re-runs only cells BELOW the changed + widget). + +## The engagement-data cell contract + +```python +# ── Engagement data — EDIT PER ENGAGEMENT (tagged: engagement-data) ── +ENGAGEMENT: dict[str, object] = { + "client": "", # e.g. "Acme Corp" — shown on the stage board header + "workshop_date": "", # ISO date, e.g. "2026-08-12" + "facilitator": "", + "attendees": (), # tuple of "Name — role" strings +} +``` + +- The **master ships placeholders** (empty strings / empty tuple); the engagement copy + ships real values. Everything that checks this cell — the gate, `pytest`, the repo + suite — pins **shape only** (key set, types), NEVER emptiness or a value, so master + and filled copy both stay green. +- Where the values flow: the stage board header (rendered only when `client` is + non-empty, so the master's stage is unchanged), the export preamble, and the + appendix JSON `meta`. +- Extend the dict per instrument (e.g. `current_spend`, `agent_count` for a sizing + assessment) — client data belongs HERE, in the cell, not in the engine and not + hardcoded in presentation cells. + +## Engine parameterization + +The engine package holds schema + logic only, and every session-level function takes +the content as its **first argument** — the engine never imports content: + +```python +SESSION = build_session(TOPICS, STATUS_BY_TOPIC, NOTES_BY_TOPIC, DONE_SUBTOPICS) +``` + +*Why:* content stays in the notebook (editable), logic stays testable — tests feed the +real bank from the tagged cell, or synthetic banks for edge cases. Widgets are built by +a **runtime loop over the content** (with per-widget distinct labels — Mercury's cache +is label-keyed), so a content edit propagates to sidebar, board, script, gate, and +export with zero further wiring. + +--- + +## Testing an Assessment + +All layers of the Mercury Pattern's testing section apply; assessments add the +**exec-by-tag** recipe for content: + +```python +# tests/conftest.py — the content fixtures read the NOTEBOOK, no kernel +def tagged_cell_ns(tag: str) -> dict[str, Any]: + nb = nbformat.read(NOTEBOOK, as_version=4) + cells = [c for c in nb.cells if tag in c.metadata.get("tags", [])] + assert len(cells) == 1 + ns: dict[str, Any] = {} + exec(compile(cells[0].source, f"{NOTEBOOK.name} [{tag}]", "exec"), ns) + return ns +``` + +- **Content pins** — counts, key order, uniqueness, well-formedness of the bank + (`tests/test_topics.py` in the reference). Re-pin deliberately on content changes. +- **Engagement pins** — shape of `ENGAGEMENT`, its flow into the export meta, and the + cell's above-the-widgets position (`tests/test_engagement.py`). +- **Engine pins** — session logic against hand-checked values (`tests/test_session.py`). +- **Staging** — `backstage`/`backstage_md` render only off stage. +- **Gate + repo suite** — the in-notebook gate re-counts the bank on every execution; + `make check-notebooks` (repo root) enforces the tag taxonomy from outside. + +## Export handoff (the LLM-input artifact) + +`scripts/export_report.py` — execute ONCE, convert twice, post-process: + +1. `nbconvert --execute` to a temp copy (never combine `--execute` with tag-stripping + in one call — a cell could be removed before it runs). +2. **HTML** from the executed copy — full presentation, human review. +3. **Markdown** from the executed copy with + `TagRemovePreprocessor.remove_cell_tags={"presentation"}` — the LLM artifact keeps + content, script, gate, and appendix; drops setup/widget/board source and widget-repr + noise. +4. Prepend a generated **preamble**: what the document is, the engagement line (read + from the `engagement-data` cell via nbformat), how to read it, and the instruction + that the final fenced JSON block is the source of truth. + +The appendix cell emits its table + JSON through **`backstage_md()`** (one +`text/markdown` display), so the `.md` export carries a clean fenced ```json block as +the last thing in the file — not an indented text blob. + +## Stage polish + +`config.toml` carries the brand ([`docs/brand.md`](brand.md)) — the reference's file +is the copy-me. Beyond the palette: name the deliverable notebook in +`[welcome] message`, keep the footer attributed, set the brand state colors +(`success_color`, `warning_color`, `danger_color`), and keep **web-safe font stacks** +(no `font_url` — a client screen must not depend on a network font fetch). Engine +palettes may deviate deliberately (the reference's on-board complete-green `#00a34c` +is darker than brand Success `#00cb5d` for contrast on white cards) — note such +choices where they live. + +--- + +## Copy-out checklist (canonical) + +Running an assessment for a client — the master never touches client data: + +1. **Copy** the master directory out of Palladium to your engagement location + (it is self-contained — no repo machinery comes along or is needed). +2. **Rename** it `YYYYMM_Client_Instrument` (e.g. `202608_Acme_CX_Discovery`). +3. **Provision**: `python -m venv .venv && .venv/bin/pip install -e ".[dev]"` + (editable installs pin absolute paths — a copied venv is broken; always recreate). +4. **Fill the `engagement-data` cell** — and only that cell — with the client facts. +5. **Verify**: `pytest` and the headless gate + (`jupyter nbconvert --to notebook --execute --inplace notebooks/*.ipynb`) — both + stay green by design (shape-only pins). +6. The copy is now **confidential**: it never merges back, never returns to this + repo. Improvements you discover on the engagement are hand-carried to the master + as clean, client-free edits. + +## Assessment anti-patterns + +- ❌ **Facilitator prose in markdown cells** — markdown ALWAYS renders on the + Mercury stage; run-books, "backstage" explanations, and section headings for + hidden sections all leak to the client. The stage gets a client-neutral title + and the board; facilitator orientation goes through `backstage_md()` in a + `presentation`-tagged code cell (JupyterLab and the HTML export show it; the + stage and the LLM `.md` export don't). +- ❌ **Client data in a master** — a client's name in `assessments/` means the + copy-out step was skipped; scrub and move it out. +- ❌ **Content in `.py`** — topic banks, prompts, survey text are content; content + lives in tagged cells. (This includes notebook *generators* that hold content as + Python strings — the same mistake one indirection deeper.) +- ❌ **Untagged content cells** — the repo suite, the tests, and the export pipeline + all find content by tag; an untagged content cell is invisible to all three. +- ❌ **Emptiness pins** — asserting `ENGAGEMENT["client"] == ""` breaks every filled + engagement copy; pin shape, never values. +- ❌ **Widget labels derived from unstable text** — labels come from content titles; + editing a title mid-session resets that widget. Edit titles between sessions. diff --git a/docs/Athena_TEI.md b/docs/Athena_TEI.md index def9053..50b5ff1 100644 --- a/docs/Athena_TEI.md +++ b/docs/Athena_TEI.md @@ -690,3 +690,27 @@ All arithmetic uses Python `Decimal` to avoid floating-point drift. Values are s --- *TEI Tool — Athena* + +--- + +## Palladium conventions (client-side) + +Relocated from the root README (2026-07); these are Palladium's bridges where the +Forrester methodology and the Athena TEI API differ. + +### Methodology bridges (Palladium ↔ Athena) + +| Topic | Athena behaviour | Palladium convention | +|---|---|---| +| **Cost risk adjustment** | Costs are never risk-adjusted server-side | Cost values are pushed pre-multiplied by `(1 + risk_adj)`; field-level adjustment stays 0 | +| **Year-0 "Initial" costs** | No year-0 concept; non-annual values are folded into Year 1 | Each cost gets a companion non-annual `_initial` field. `TEIClient` folds them back into an `initial` key on read. Athena discounts these as Year 1 (Forrester doesn't discount Year 0) — expect ≈0.15% drift on cost PV | + +### Scenario analysis (`core.calculations.SCENARIOS`) + +Three scenarios model uncertainty in adoption and realization: + +| Scenario | Adoption | Risk delta | Effect | +|----------|----------|------------|--------| +| Conservative | 80% | +10pp on benefits | Lower benefits, higher modelled cost | +| Moderate | 100% | 0 | Base case (= published study) | +| Aggressive | 115% | –5pp on benefits | Higher benefits, lower padding on cost | diff --git a/docs/Mercury_Notebook_Pattern_V1-00.md b/docs/Mercury_Notebook_Pattern_V1-00.md index 20b1461..b69a686 100644 --- a/docs/Mercury_Notebook_Pattern_V1-00.md +++ b/docs/Mercury_Notebook_Pattern_V1-00.md @@ -1,21 +1,32 @@ -# Mercury Notebook Deliverable Pattern v1.0.0 +# Mercury Notebook Deliverable Pattern v1.1.0 -Standardizes how Palladium studies ship business-case deliverables: a Mercury-served -Jupyter notebook **is** the artifact — math in a self-contained study package, +Standardizes how Palladium masters — **Studies and Assessments** — ship notebook +deliverables: a Mercury-served Jupyter notebook **is** the artifact — logic in a +self-contained study package, content and client data in tagged notebook cells, presentation in reactive notebooks, verification gates pinning every number, and -LLM-readable exports. All new studies follow this pattern; the Streamlit app path is +LLM-readable exports. All masters follow this pattern; the Streamlit app path is retired by it. ## 🐾 Red Panda Approval™ -This pattern follows Red Panda Approval standards. +This pattern follows Red Panda Approval standards (see `CLAUDE.md` for the rubric). **Audience note:** this document is written to be loaded whole as context by an LLM -agent building or modifying a study. Rules are imperative (MUST/SHOULD/NEVER), each +agent building or modifying a master. Rules are imperative (MUST/SHOULD/NEVER), each with a one-line *why*. Long code lives in the runnable template [`template/MercuryNotebook/`](../template/MercuryNotebook/) — copy it to start a study; -snippets here are excerpts from it. The full-scale reference implementation is the CTM -Genesys study, [`studies/202607_CTM_GenesysCX/`](../studies/202607_CTM_GenesysCX/). +snippets here are excerpts from it (note: the template still encodes the py-engine +model only). The reference implementation of the **notebook-first content model** is +[`assessments/CX_Discovery_Workshop/`](../assessments/CX_Discovery_Workshop/); the +largest worked multi-notebook example remains the CTM Genesys study, +[`studies/202607_CTM_GenesysCX/`](../studies/202607_CTM_GenesysCX/). + +**Docs map:** this file holds the **shared mechanics** every master obeys. +[`Assessment_Pattern_V1-00.md`](Assessment_Pattern_V1-00.md) and +[`Study_Pattern_V1-00.md`](Study_Pattern_V1-00.md) specialize it per master type — +read the one for your master type first. `CLAUDE.md` at the repo root is the +always-on contract (rubric, three-layer contract, taxonomy, risk tiers, +confidentiality) and takes precedence where documents disagree. --- @@ -53,9 +64,11 @@ Instead, this pattern defines: ``` palladium/ -├── docs/ # repo-wide docs (this pattern, brand.md) +├── docs/ # repo-wide docs (the patterns, brand.md) ├── template/ │ └── MercuryNotebook/ # copy-me starting point (runnable) +├── assessments/ +│ └── Instrument_Name/ # reusable workshop master (UNDATED), e.g. CX_Discovery_Workshop └── studies/ ├── YYYYMM_TEI_Vendor_Product/ # vendor TEI study, e.g. 202602_TEI_Amazon_Connect └── YYYYMM_Client_EngagementName/ # client study, e.g. 202607_CTM_GenesysCX @@ -63,6 +76,10 @@ palladium/ - Names MUST use **underscores, never dashes** — dashed directories can't be Python packages, and everything in a study is importable code. +- **Studies are dated** (`YYYYMM_` — they reproduce a dated publication or engagement); + **Assessments are undated** (living instruments). An **engagement copy** of a master + is stamped `YYYYMM_Client_Instrument` at copy-time and lives OUTSIDE this repo — see + the copy-out checklist in the Assessment Pattern and `CLAUDE.md` § Confidentiality. - Every study is self-contained with this layout (from the template): ``` @@ -93,11 +110,11 @@ JupyterLab by analysts, executed headless by nbconvert for exports. NEVER build parallel UI (Streamlit, Dash, a second "app" rendering the same model) — two surfaces over one model always diverge, and the notebook already *is* the interactive surface. -### 2 · Engine/presentation split +### 2 · Engine/presentation split — and where content lives Notebooks hold **no math**. Every number a stakeholder sees is computed in the study -package and imported. *Why:* the package is testable and diffable; notebook cells are -neither. The test suite pins the engine; the notebook only arranges its outputs. +package and imported. *Why:* the package is testable and diffable for *logic*; the +test suite pins the engine; the notebook only arranges its outputs. ```python # notebook cell — arrange and render, never compute @@ -106,6 +123,15 @@ inc, net_by = case_flows(total_by_year, ben_by_year) kpi = case_kpis(inc, net_by, DISCOUNT_RATE) ``` +The split cuts the other way for **content and client data**: notebooks hold no math, +but they DO hold the human-authored content (topic banks, survey text, facilitation +prompts) and the client facts (spend, headcount, engagement identity) — in **tagged +cells** (`topic-bank`, `engagement-data`), edited in Jupyter. Content NEVER lives in a +`.py` file. *Why:* the notebook is the document the consultant reads and edits; +content hidden in importable modules defeats the point of a notebook. Tests pin +content by reading the tagged cells with nbformat and exec'ing them — no kernel +needed. Full contract: the [Assessment Pattern](Assessment_Pattern_V1-00.md). + ### 3 · The Mercury reactivity contract Mercury re-executes only the cells **below** a changed widget's cell — never the @@ -416,8 +442,15 @@ Each of these cost a debugging session or a client-facing embarrassment. Don't. ## Testing -Layers, from inner to outer — every study ships all four: +Layers, from inner to outer — every master ships layers 1–4; layer 0 covers the whole +library from the repo root: +0. **Repo-level structural suite** (`tests/test_notebooks.py`, run from the ROOT venv: + `make check-notebooks`) — kernel-free nbformat checks on every committed notebook: + parses, python3 kernel, no error outputs, cleanly executed top-to-bottom, and (for + notebook-first masters) the tagged-cell taxonomy. Every notebook must be classified + in `tests/nbcheck.py`; grandfathered ones skip the notebook-first tier with a + recorded reason. 1. **Engine pins** (`tests/`) — hand-checked acceptance numbers for every model function; the contracted overlay AND the verbatim record both pinned, so neither can drift. Compute by hand first, then pin (see diff --git a/docs/Notebook_Review_Prompt_V1-00.md b/docs/Notebook_Review_Prompt_V1-00.md new file mode 100644 index 0000000..56d1aec --- /dev/null +++ b/docs/Notebook_Review_Prompt_V1-00.md @@ -0,0 +1,94 @@ +# Notebook Review Prompt v1.0.0 + +The canonical LLM review prompt for a Palladium master notebook. Model-agnostic: +paste everything below the line into any capable LLM together with the notebook's +raw `.ipynb` JSON, or run it in-repo with `/review-notebook `. It hunts the +class of problems deterministic checks can't: content oddities, cross-cell rot, +stale numbers, and stage leakage. + +--- + +## Role + +You are reviewing one **Palladium master notebook** — a Mercury-served Jupyter +deliverable a client will see on a live call. Your input is the raw `.ipynb` JSON +(cell sources, tags, outputs, in document order). Before judging anything, read the +contracts it was built against: + +- `docs/Mercury_Notebook_Pattern_V1-00.md` — reactivity, gate, stage/backstage, + appendix +- `docs/Assessment_Pattern_V1-00.md` or `docs/Study_Pattern_V1-00.md` — whichever + matches the master's type (assessments/… vs studies/…) +- The master's own `README.md` and, for cross-checks, its engine package + (`discoverylib/`, `teicalc/`, …) — you MAY open engine `.py` files to verify a + claim, and should when a number or name is in doubt. + +## What NOT to re-check + +`tests/test_notebooks.py` already enforces these deterministically — do not spend +review effort or findings on them: the file parses as nbformat 4; kernel is python3; +no error outputs; execution counts are monotonic top-to-bottom; the unique tags +(`topic-bank`, `engagement-data`, `gate`, `data-appendix`) appear at most once; +notebook-first masters have all four, the appendix last, engagement-data above the +widgets; presentation cells emit no stream output; content cells use no magics. + +## Check catalogue + +Work cell by cell in document order, then once more end-to-end per theme: + +1. **Content oddities** — typos and grammar in client-visible text; duplicated or + near-duplicate prompts/items; terminology drifting from the source document the + content anchors (the README names it); `key` slugs that don't match their titles; + counts/minutes in comments or prose that don't match the actual content; tone that + doesn't belong in front of a client. + *Spot it by:* reading the content cells as an editor, then recounting anything a + comment or heading claims. +2. **Broken cross-cell references** — names used before their defining cell in + document order; a widget's `.value` read in the cell that defines it (frozen at + first render); variables shadowed or redefined with a different meaning; cells + that only work because of stale kernel state (would fail on a fresh top-to-bottom + run); imports used but not imported in any earlier cell. + *Spot it by:* tracing each name in a cell back to its defining cell index. +3. **Stale numbers** — figures hardcoded in markdown or annotations that the engine + or gate could contradict; outputs inconsistent with the current source (a changed + cell whose committed output still shows the old result); gate pins that disagree + with the test-suite pins; totals in prose that don't equal the content. + *Spot it by:* comparing markdown claims ↔ committed outputs ↔ gate asserts ↔ + engine constants. +4. **Tag/metadata issues** — content sitting in an untagged cell (invisible to + tests and the export pipeline); a cell whose comment says one thing and whose tag + says another; presentation logic inside a content cell or vice versa. + *Spot it by:* asking, for each cell, "who consumes this — and would they find it?" +5. **Stage leakage** — anything client-inappropriate reachable on the Mercury stage: + markdown cells ALWAYS render on stage, so internal notes/instructions in markdown + are leaks; diagnostics via bare `print` instead of `backstage()`; placeholder or + internal wording in the board/stage HTML; a filled engagement value where the + master should have a placeholder. + *Spot it by:* simulating the stage — markdown cells + non-backstage outputs of + code cells are what the client sees. +6. **Export quality** — appendix JSON missing state a figure or table shows; + values that won't JSON-serialize; the `.md` handoff missing something an LLM + would need to draft the write-up; preamble claims that don't match the document. + *Spot it by:* reading the appendix cell's payload against everything rendered + above it. + +## Output format + +Report findings **ordered by severity**, one line each, exactly: + +``` +[BLOCKER] cell (): +[SHOULD-FIX] cell (): +[NIT] cell (): +``` + +- **BLOCKER** — a client would see something wrong, or the notebook lies (stale + number, stage leak, broken reference). +- **SHOULD-FIX** — correctness/maintainability debt that won't embarrass anyone + today. +- **NIT** — polish. + +End with one verdict line: `VERDICT: blocker(s), should-fix, nit(s) — +`. If a theme produced no findings, do not pad — +finding nothing is a valid result; say `No findings.` above the verdict if the whole +review is clean. Output nothing outside this format. diff --git a/docs/Study_Pattern_V1-00.md b/docs/Study_Pattern_V1-00.md new file mode 100644 index 0000000..10a9c11 --- /dev/null +++ b/docs/Study_Pattern_V1-00.md @@ -0,0 +1,107 @@ +# Study Pattern v1.0.0 + +How Palladium ships **Studies**: dated reproductions of a published base document — +a Forrester TEI study or similar — personalized to a client as an overlay. A Study is +a **master**: the anchor is public/published data, the master stays client-clean, and +client personalization happens in an engagement copy outside this repo. References: +[`studies/202512_TEI_Genesys_CX_Cloud/`](../studies/202512_TEI_Genesys_CX_Cloud/) and +[`studies/202602_TEI_Amazon_Connect/`](../studies/202602_TEI_Amazon_Connect/) +(both package `teicalc`). + +## 🐾 Red Panda Approval™ + +This pattern follows Red Panda Approval standards (see `CLAUDE.md` for the rubric). + +**Audience note:** written to be loaded whole by an LLM agent building or modifying a +study. This document holds what studies ADD to the +[Mercury Notebook Deliverable Pattern](Mercury_Notebook_Pattern_V1-00.md) — shared +mechanics live there and are not restated. `CLAUDE.md` takes precedence where +documents disagree. + +**Status note — read this before "fixing" a study:** this pattern is grounded in the +TEI twins **as they are today**: math and anchors in the `.py` engine, hand-authored +notebooks, no tagged-cell taxonomy. The notebook-first evolution — client data in an +`engagement-data` cell, tagged content cells, the Assessment Pattern's cell taxonomy — +**lands with the first study redesign** (a recorded identified opportunity in +`CLAUDE.md`), not by piecemeal edits. Until then the repo-level structural suite +grandfathers the study notebooks by name, and their current shape is the correct +shape. `studies/202607_CTM_GenesysCX/` is a grandfathered real-client engagement +study — frozen; see `CLAUDE.md` § Known liabilities. + +--- + +## What a Study is + +| | **Study** | Assessment | +|---|---|---| +| Reproduces | a **dated publication** (TEI) or signed engagement | — | +| Naming | `YYYYMM_TEI_Vendor_Product` / `YYYYMM_Client_Engagement` | undated | +| Anchor | the publication's composite, **verbatim** | the instrument's content | +| "Numbers" | dollars — NPV / ROI / payback | qualitative state | +| Client data | overlay rescale on the anchor | `engagement-data` cell | + +The `YYYYMM_` prefix is the **publication/engagement date** (e.g. `202512_` = the +December 2025 Forrester study), not the date you worked on it — a Study is a snapshot +of a dated document and stays dated. + +## Verbatim anchor + contracted overlay + +The heart of a Study (mechanics in the Mercury Pattern, *Recommended Practices*): + +- `anchor.py` holds the publication's tables as `*_VERBATIM` constants — **NEVER + edited**. The reproduction is only worth anything if the anchor is the exact + published record. +- `overlay.py` layers client personalization over the anchor — in the current twins, a + 🟡 first-order linear rescale by client drivers (agents / contacts / growth): + "the composite at your size", never presented as "your TEI". +- Notebooks read through the `anchor()` helper so the walk *published → personalized* + stays explicit and auditable on stage. + +## The reproduction gate + +A Study's gate (Mercury Pattern §4) MUST pin the **published headline numbers** — +the reproduction is the smoke test: + +- Amazon Connect twin: NPV **$78,713,715**, ROI **342%**, payback **<6 months**. +- Genesys CX Cloud twin: NPV **$10,783,468**, ROI **266%**. + +Plus the **composite-scale identity**: the overlay at the composite's own drivers must +reproduce the anchor exactly (rescale(1.0) == anchor) — the proof the personalization +layer adds nothing at scale 1. + +## Confidentiality posture + +- The **published source PDF** (the Forrester study) is public and MAY live in the + study's `docs/` — it is the one exception to the binary-documents block in + `.gitignore`: add it with `git add -f` and name the source in the commit message. +- Everything else follows `CLAUDE.md` § Confidentiality: masters client-clean, client + personalization only in an engagement copy outside the repo, and NEVER commit client + documents (SOWs, quotes, NDA'd vendor decks). The CTM study predates this rule and + is grandfathered — do not use it as precedent. + +## Layout & testing + +Layout and packaging are the Mercury Pattern's (self-contained `teicalc`-style engine, +per-study venv, whole toolchain as core deps). Testing is the standard four layers +plus repo-level layer 0; a Study's engine pins carry BOTH the verbatim record and the +overlay, so neither can drift: + +``` +tests/test_anchor.py # the published tables, pinned line by line +tests/test_overlay.py # rescale identity at composite scale + spot rescales +tests/test_scenarios.py # scenario framing over the overlay +tests/test_model.py # finance primitives (NPV, payback) hand-checked +tests/test_staging.py # stage/backstage +``` + +## Variants + +The Mercury Pattern's variants map onto Studies as: + +- **Variant 4 — TEI composite reproduction** — the canonical Study (both twins). +- **Variant 1 — corrected/pressure-tested business case** — engagement-study form + (CTM's `ctm_business_case_corrected`): vendor claims verbatim, omitted costs added. +- **Variant 2 — scenario notebook on a thin module** — a second question over the same + engine (CTM's `migration_wfm`). +- **Variant 3 — exploratory calculator** — pre-anchor what-if surface that graduates + into Variant 1 as facts arrive. diff --git a/pyproject.toml b/pyproject.toml index 614d41c..8746185 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "pandas>=2.0", "plotly>=5.18", "numpy>=1.26", + "nbformat>=5.9", ] [project.optional-dependencies] @@ -27,7 +28,7 @@ palladium = "core.cli.main:main" [tool.setuptools.packages.find] include = ["core*", "palladium*"] -exclude = ["tests*", "studies*", "docs*"] +exclude = ["tests*", "studies*", "assessments*", "docs*"] [tool.pytest.ini_options] testpaths = ["tests"] @@ -44,4 +45,5 @@ ignore = ["E501"] # line length handled by formatter [tool.ruff.lint.per-file-ignores] "studies/*/notebooks/*.ipynb" = ["E402"] +"assessments/*/notebooks/*.ipynb" = ["E402"] "tests/*" = ["F401"] diff --git a/requirements.txt b/requirements.txt index d50ff81..47d7a4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,6 @@ jupyter>=1.0 pandas>=2.0 plotly>=5.18 numpy>=1.26 +nbformat>=5.9 pytest>=7.4 ruff>=0.1 diff --git a/studies/202607_CTM_GenesysCX/docs/README.md b/studies/202607_CTM_GenesysCX/docs/README.md new file mode 100644 index 0000000..42b7394 --- /dev/null +++ b/studies/202607_CTM_GenesysCX/docs/README.md @@ -0,0 +1,6 @@ +# Confidential source material — grandfathered + +The client/vendor documents in this directory were committed before the +confidentiality rule existed (see `CLAUDE.md`, *Confidentiality* and *Known +liabilities*). They are grandfathered: **do not add more**. Extraction of this +study to an engagement archive is a recorded follow-up. diff --git a/studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_corrected.ipynb b/studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_corrected.ipynb index 0372055..5104786 100644 --- a/studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_corrected.ipynb +++ b/studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_corrected.ipynb @@ -10135,16 +10135,16 @@ "label": "NA — current platform cost ($/yr)", "layout": "IPY_MODEL_6295cf92344b415598307483934485f3", "layout_path": null, - "max": 8000000.0, - "min": 0.0, + "max": 8000000, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 10000.0, + "step": 10000, "tabbable": null, "tooltip": null, "url_key": "", - "value": 3348969.0 + "value": 3348969 } }, "53869a7ea78b44f4a134983d354228f0": { @@ -10382,7 +10382,7 @@ "layout": "IPY_MODEL_0e3be284c70e45b7ad9fe12c3b76163f", "layout_path": null, "max": 0.6, - "min": 0.0, + "min": 0, "position": "sidebar", "render_slot_id": null, "source_cell_id": null, @@ -10415,16 +10415,16 @@ "label": "ASIA — current platform cost ($/yr)", "layout": "IPY_MODEL_61085bdb080e48e186ec10b1abee291a", "layout_path": null, - "max": 8000000.0, - "min": 0.0, + "max": 8000000, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 10000.0, + "step": 10000, "tabbable": null, "tooltip": null, "url_key": "", - "value": 2069588.0 + "value": 2069588 } }, "61085bdb080e48e186ec10b1abee291a": { @@ -10555,16 +10555,16 @@ "label": "CCaaS licence run-rate ($/yr) — contracted", "layout": "IPY_MODEL_3c767a6b472d42c89e6b7baf705fcbbc", "layout_path": null, - "max": 10000000.0, - "min": 0.0, + "max": 10000000, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 100000.0, + "step": 100000, "tabbable": null, "tooltip": null, "url_key": "", - "value": 3200000.0 + "value": 3200000 } }, "6ce50fa7157042a4be99b19f24be3ce7": { @@ -10642,16 +10642,16 @@ "label": "ANZ — current platform cost ($/yr)", "layout": "IPY_MODEL_54f1095af078476ba27715f8826aa967", "layout_path": null, - "max": 8000000.0, - "min": 0.0, + "max": 8000000, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 10000.0, + "step": 10000, "tabbable": null, "tooltip": null, "url_key": "", - "value": 677320.0 + "value": 677320 } }, "79ec042073404a6da4ec639b6b44a57c": { @@ -11267,16 +11267,16 @@ "label": "EMEA — current platform cost ($/yr)", "layout": "IPY_MODEL_8309c39462b34cc5afad42785a0c53ff", "layout_path": null, - "max": 8000000.0, - "min": 0.0, + "max": 8000000, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 10000.0, + "step": 10000, "tabbable": null, "tooltip": null, "url_key": "", - "value": 1204124.0 + "value": 1204124 } }, "d69203bdb8584fc9975e475199fcc235": { @@ -11301,16 +11301,16 @@ "label": "Genesys ramp — licence-free months", "layout": "IPY_MODEL_7b92a3a1d4ec49049e22687c38236912", "layout_path": null, - "max": 24.0, - "min": 0.0, + "max": 24, + "min": 0, "position": "inline", "render_slot_id": null, "source_cell_id": null, - "step": 1.0, + "step": 1, "tabbable": null, "tooltip": null, "url_key": "", - "value": 6.0 + "value": 6 } }, "d7d4d74246ea490da7610c277c802b99": { @@ -11335,8 +11335,8 @@ "label": "AI Translate eligibility 🟡", "layout": "IPY_MODEL_1bf396a231374a2097e03bf45fc874ff", "layout_path": null, - "max": 1.0, - "min": 0.0, + "max": 1, + "min": 0, "position": "sidebar", "render_slot_id": null, "source_cell_id": null, @@ -11521,7 +11521,7 @@ "layout": "IPY_MODEL_bf7b6f3bca5f48e6838dc54f84943731", "layout_path": null, "max": 0.5, - "min": 0.0, + "min": 0, "position": "sidebar", "render_slot_id": null, "source_cell_id": null, diff --git a/studies/202607_CTM_GenesysCX/notebooks/ctm_token_calculator.ipynb b/studies/202607_CTM_GenesysCX/notebooks/ctm_token_calculator.ipynb index b557a9c..74d9abc 100644 --- a/studies/202607_CTM_GenesysCX/notebooks/ctm_token_calculator.ipynb +++ b/studies/202607_CTM_GenesysCX/notebooks/ctm_token_calculator.ipynb @@ -42,7 +42,7 @@ } ], "source": [ - "# ── Setup ──────────────────────────────────────────────────────────\n", + "# ── Setup ──────────────────────────3rd with just the────────────────────────────────\n", "import sys, pathlib\n", "_ROOT = pathlib.Path.cwd()\n", "if not (_ROOT / \"tokencalc\").exists(): # notebook lives in notebooks/\n", @@ -8847,7 +8847,7 @@ "disabled": false, "layout": "IPY_MODEL_d68795b0944341bf8354500e127e4e59", "max": 0.5, - "min": 0.0, + "min": 0, "orientation": "horizontal", "readout": true, "readout_format": ".0%", @@ -8940,7 +8940,7 @@ "description_allow_html": false, "disabled": false, "layout": "IPY_MODEL_3462a88bfec94f01b6aea7530a431884", - "max": 5.0, + "max": 5, "min": 0.5, "orientation": "horizontal", "readout": true, @@ -9105,7 +9105,7 @@ "disabled": false, "layout": "IPY_MODEL_9fe19d91747e4865a104e65b658b7c8e", "max": 0.7, - "min": 0.0, + "min": 0, "orientation": "horizontal", "readout": true, "readout_format": ".0%", @@ -9334,7 +9334,7 @@ "description_allow_html": false, "disabled": false, "layout": "IPY_MODEL_fd591354d1214f4b9b7d8850be81a511", - "max": 1.0, + "max": 1, "min": 0.3, "orientation": "horizontal", "readout": true, @@ -9434,7 +9434,7 @@ "description_allow_html": false, "disabled": false, "layout": "IPY_MODEL_4c9514e9b96142e9a269035bdd0714c4", - "max": 1.0, + "max": 1, "min": 0.3, "orientation": "horizontal", "readout": true, @@ -9483,7 +9483,7 @@ "disabled": false, "layout": "IPY_MODEL_995b326fefa942da94ce18e92254860e", "max": 0.3, - "min": 0.0, + "min": 0, "orientation": "horizontal", "readout": true, "readout_format": ".0%", diff --git a/studies/202607_CX_Discovery_Workshop/scripts/export_report.py b/studies/202607_CX_Discovery_Workshop/scripts/export_report.py deleted file mode 100644 index 5c4dbaa..0000000 --- a/studies/202607_CX_Discovery_Workshop/scripts/export_report.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Export the discovery notebook as an LLM-readable report source. - -Executes the notebook fresh (widget defaults — or whatever you've captured and -saved in the notebook), then writes both formats to exports/: - - exports/cx_discovery.html — human-reviewable - exports/cx_discovery.md — leanest LLM input - -The client-facing board is HTML the export carries, but the machine payload — -every topic's status, sub-topic tally, and captured notes — lives in the -backstage data-appendix cell (markdown table + JSON), so the exported `.md` is -complete input for drafting the survey write-up or seeding a business case. - -Run from the study root: python scripts/export_report.py -""" -from __future__ import annotations - -import subprocess -import sys -from pathlib import Path - -ROOT = Path(__file__).resolve().parent.parent -NOTEBOOKS = [ - ROOT / "notebooks" / "cx_discovery.ipynb", -] -EXPORTS = ROOT / "exports" - - -def main() -> None: - picked = [nb for nb in NOTEBOOKS - if len(sys.argv) < 2 or sys.argv[1] in nb.name] - if not picked: - sys.exit(f"no notebook matches {sys.argv[1]!r}") - EXPORTS.mkdir(exist_ok=True) - for nb in picked: - for fmt in ("html", "markdown"): - subprocess.run( - [sys.executable, "-m", "nbconvert", "--execute", - "--to", fmt, "--output-dir", str(EXPORTS), str(nb)], - check=True, cwd=ROOT, - ) - for p in sorted(EXPORTS.iterdir()): - if p.suffix in (".html", ".md"): - print(f"wrote {p.relative_to(ROOT)} ({p.stat().st_size / 1024:,.0f} KB)") - - -if __name__ == "__main__": - main() diff --git a/studies/202607_CX_Discovery_Workshop/tests/test_staging.py b/studies/202607_CX_Discovery_Workshop/tests/test_staging.py deleted file mode 100644 index 63ff857..0000000 --- a/studies/202607_CX_Discovery_Workshop/tests/test_staging.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Stage/backstage detection — Mercury kernels carry MERCURY_CONFIG_DIR.""" - -from discoverylib import staging - - -def test_backstage_prints_only_off_stage(monkeypatch, capsys): - monkeypatch.delenv("MERCURY_CONFIG_DIR", raising=False) - assert not staging.on_stage() - staging.backstage("visible") - assert capsys.readouterr().out == "visible\n" - - monkeypatch.setenv("MERCURY_CONFIG_DIR", "/tmp/app") - assert staging.on_stage() - staging.backstage("hidden") - assert capsys.readouterr().out == "" diff --git a/tests/nbcheck.py b/tests/nbcheck.py new file mode 100644 index 0000000..c934028 --- /dev/null +++ b/tests/nbcheck.py @@ -0,0 +1,117 @@ +"""Structural checks for every master notebook in the library. + +Kernel-free: everything here reads the committed ``.ipynb`` JSON with +nbformat — no study venv, no execution. Content and engine pins stay in +each master's own ``tests/`` (run in its venv); this layer guards only +STRUCTURE: the notebook parses, was executed cleanly top-to-bottom, and +(for notebook-first masters) carries the tagged-cell taxonomy the +Assessment Pattern requires. + +Classification is explicit and non-silent: every notebook on disk must be +listed in exactly one of NOTEBOOK_FIRST or GRANDFATHERED (a completeness +test enforces it), so a new master cannot dodge the suite, and every +exemption carries its reason — grandfathered notebooks run the structural +tier and skip the notebook-first tier with that reason shown by +``pytest -rs``. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import nbformat + +REPO = Path(__file__).resolve().parent.parent + +# Masters built on the notebook-first content model (Assessment Pattern): +# full check set, including the tagged-cell taxonomy. +NOTEBOOK_FIRST = { + "assessments/CX_Discovery_Workshop/notebooks/cx_discovery.ipynb", +} + +# Structural tier only, each with its recorded reason (see CLAUDE.md, +# Known liabilities). Redesigning one of these to notebook-first means +# moving it up to NOTEBOOK_FIRST — never deleting it from here silently. +GRANDFATHERED = { + "assessments/CX_AI_Diagnostic/notebooks/diagnostic.ipynb": + "generated notebook; pre-dates the notebook-first model (redesign pending)", + "studies/202512_TEI_Genesys_CX_Cloud/notebooks/business_case.ipynb": + "pre-tag TEI master (redesign pending)", + "studies/202602_TEI_Amazon_Connect/notebooks/business_case.ipynb": + "pre-tag TEI master (redesign pending)", + "studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_corrected.ipynb": + "real-client study, frozen (see CLAUDE.md known liabilities)", + "studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_no_current_state.ipynb": + "real-client study, frozen (see CLAUDE.md known liabilities)", + "studies/202607_CTM_GenesysCX/notebooks/ctm_business_case_virtual_agents.ipynb": + "real-client study, frozen (see CLAUDE.md known liabilities)", + "studies/202607_CTM_GenesysCX/notebooks/ctm_migration_wfm.ipynb": + "real-client study, frozen (see CLAUDE.md known liabilities)", + "studies/202607_CTM_GenesysCX/notebooks/ctm_token_calculator.ipynb": + "real-client study, frozen (see CLAUDE.md known liabilities)", + "template/MercuryNotebook/notebooks/business_case.ipynb": + "template encodes the py-engine model (rework pending)", +} + +ALL_CLASSIFIED = sorted(NOTEBOOK_FIRST | set(GRANDFATHERED)) + +# Tags that may appear at most once per notebook. +UNIQUE_TAGS = ("topic-bank", "engagement-data", "gate", "data-appendix") + + +def discover() -> list[str]: + """Every notebook on disk under the master roots (repo-relative).""" + found: list[str] = [] + for base in ("studies", "assessments", "template"): + root = REPO / base + if not root.is_dir(): + continue + for p in root.rglob("*.ipynb"): + if ".ipynb_checkpoints" in p.parts or ".venv" in p.parts: + continue + if p.parent.name != "notebooks": + continue + found.append(p.relative_to(REPO).as_posix()) + return sorted(found) + + +def load(rel: str) -> Any: + return nbformat.read(REPO / rel, as_version=4) + + +def cell_tags(cell: Any) -> list[str]: + return list(cell.metadata.get("tags", [])) + + +def cells_tagged(nb: Any, tag: str) -> list[int]: + return [i for i, c in enumerate(nb.cells) if tag in cell_tags(c)] + + +def execution_problem(nb: Any) -> str | None: + """None if executed cleanly top-to-bottom, else what's wrong. + + Non-empty code cells must carry integer execution counts, strictly + increasing 1..N in document order (proof of one clean linear run); + empty cells may be unexecuted (``None``). + """ + prev = 0 + for i, c in enumerate(nb.cells): + if c.cell_type != "code" or not c.source.strip(): + continue + ec = c.get("execution_count") + if not isinstance(ec, int): + return f"cell {i} has no execution count (notebook not executed?)" + if ec != prev + 1: + return f"cell {i} has execution count {ec}, expected {prev + 1}" + prev = ec + return None + + +def error_outputs(nb: Any) -> list[int]: + return [ + i + for i, c in enumerate(nb.cells) + if c.cell_type == "code" + and any(o.get("output_type") == "error" for o in c.get("outputs", [])) + ] diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py new file mode 100644 index 0000000..0f00437 --- /dev/null +++ b/tests/test_notebooks.py @@ -0,0 +1,163 @@ +"""Repo-level notebook validation — structure only, kernel-free. + +Layer 0 of the testing story (see CLAUDE.md and the pattern docs): runs +from the ROOT venv against every master's committed ``.ipynb``. Content +and engine pins live in each master's own ``tests/``. + +Tiers (defined in ``nbcheck.py``): + structural — every notebook: parses, python3 kernel, no error outputs, + cleanly executed top-to-bottom, no duplicate claimed-unique + tags. + notebook-first — NOTEBOOK_FIRST masters only: the tagged-cell taxonomy + (topic-bank / engagement-data / gate / data-appendix) with + its uniqueness and position rules. GRANDFATHERED notebooks + skip this tier with their recorded reason (``pytest -rs``). +""" + +from __future__ import annotations + +import pytest + +from .nbcheck import ( + ALL_CLASSIFIED, + GRANDFATHERED, + NOTEBOOK_FIRST, + UNIQUE_TAGS, + cell_tags, + cells_tagged, + discover, + error_outputs, + execution_problem, + load, +) + + +def notebook_first_only(rel: str) -> None: + if rel in GRANDFATHERED: + pytest.skip(GRANDFATHERED[rel]) + + +# ── Classification is complete — a new master cannot dodge the suite ── +def test_every_notebook_on_disk_is_classified(): + on_disk = set(discover()) + classified = set(ALL_CLASSIFIED) + assert on_disk == classified, ( + f"unclassified notebooks: {sorted(on_disk - classified)}; " + f"classified but missing from disk: {sorted(classified - on_disk)} — " + "add each notebook to NOTEBOOK_FIRST or GRANDFATHERED (with a reason) " + "in tests/nbcheck.py" + ) + assert not (NOTEBOOK_FIRST & set(GRANDFATHERED)), "a notebook is in both tiers" + + +# ── Structural tier — every notebook ───────────────────────────────── +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_parses_as_nbformat_4(rel): + nb = load(rel) + assert nb.nbformat == 4 + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_kernel_is_python3(rel): + nb = load(rel) + assert nb.metadata.get("kernelspec", {}).get("name") == "python3" + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_no_error_outputs(rel): + assert error_outputs(load(rel)) == [] + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_executed_cleanly_top_to_bottom(rel): + problem = execution_problem(load(rel)) + assert problem is None, problem + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_no_duplicate_unique_tags(rel): + nb = load(rel) + for tag in UNIQUE_TAGS: + hits = cells_tagged(nb, tag) + assert len(hits) <= 1, f"tag {tag!r} appears in cells {hits}" + + +# ── Notebook-first tier — the Assessment Pattern tagged-cell taxonomy ─ +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_has_exactly_one_topic_bank(rel): + notebook_first_only(rel) + assert len(cells_tagged(load(rel), "topic-bank")) == 1 + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_engagement_data_present_and_above_widgets(rel): + notebook_first_only(rel) + nb = load(rel) + hits = cells_tagged(nb, "engagement-data") + assert len(hits) == 1 + # Position: client data must never re-run on a sidebar change, so the + # cell precedes the widget-defining presentation cell (identified by a + # widget constructor in its source; if a master builds widgets another + # way, its own tests carry the precise rule). + widget_cells = [ + i + for i, c in enumerate(nb.cells) + if c.cell_type == "code" + and "presentation" in cell_tags(c) + and "mr.Select(" in c.source + ] + if widget_cells: + assert hits[0] < min(widget_cells), ( + "engagement-data cell must sit above the widget cell" + ) + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_has_one_gate_with_asserts(rel): + notebook_first_only(rel) + nb = load(rel) + hits = cells_tagged(nb, "gate") + assert len(hits) == 1 + assert "assert" in nb.cells[hits[0]].source, "gate cell carries no assert" + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_data_appendix_is_last_code_cell(rel): + notebook_first_only(rel) + nb = load(rel) + hits = cells_tagged(nb, "data-appendix") + assert len(hits) == 1 + last_code = max( + i for i, c in enumerate(nb.cells) if c.cell_type == "code" + ) + assert hits[0] == last_code, "data-appendix must be the last code cell" + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_presentation_cells_do_not_print(rel): + notebook_first_only(rel) + nb = load(rel) + for i in cells_tagged(nb, "presentation"): + streams = [ + o for o in nb.cells[i].get("outputs", []) + if o.get("output_type") == "stream" + ] + assert not streams, ( + f"presentation cell {i} emits stream output — route diagnostics " + "through backstage()" + ) + + +@pytest.mark.parametrize("rel", ALL_CLASSIFIED) +def test_content_cells_are_exec_safe(rel): + # topic-bank / engagement-data are exec'd by tag in per-master tests + # and by scripts — magics or shell escapes would break that contract. + notebook_first_only(rel) + nb = load(rel) + for tag in ("topic-bank", "engagement-data"): + for i in cells_tagged(nb, tag): + for line in nb.cells[i].source.splitlines(): + stripped = line.lstrip() + assert not stripped.startswith(("%", "!")), ( + f"{tag} cell {i} uses a magic/shell escape: {line!r}" + )