From 490eaddbc1eea1aa5603b76905cb710f198b0b96 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Fri, 10 Oct 2025 14:50:55 -0700 Subject: [PATCH 001/118] Add placeholders for debugging, show & tell, and working on extensions --- 01-schedule.md | 8 ++++---- 03-materials/03-show-and-tell.md | 10 ++++++++++ 03-materials/04-debugging.md | 10 ++++++++++ 03-materials/05-working-on-your-own.md | 10 ++++++++++ 03-materials/06-developing-with-ai.md | 10 ++++++++++ 5 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 03-materials/03-show-and-tell.md create mode 100644 03-materials/04-debugging.md create mode 100644 03-materials/05-working-on-your-own.md create mode 100644 03-materials/06-developing-with-ai.md diff --git a/01-schedule.md b/01-schedule.md index 11e2eed2..7dd9aab0 100644 --- a/01-schedule.md +++ b/01-schedule.md @@ -14,10 +14,10 @@ | 9:00 AM | 5 minutes | Meet the instructors | Matt | | 9:05 AM | 15 minutes | [Exploring extensions](./03-materials/01-exploring-extensions.md) | Jason | | 9:20 AM | 50 minutes | [Anatomy of an extension](./03-materials/02-anatomy-of-extensions.md) | Jason, Matt | -| 10:10 AM | 20 minutes | Show & tell | Konstantin, Matt | +| 10:10 AM | 20 minutes | [Show & tell](./03-materials/03-show-and-tell.md) | Konstantin, Matt | | 10:30 AM | 15 minutes | โ˜•๏ธ **Coffee break!** | | -| 10:45 AM | 40 minutes | Debugging: What to do when things go wrong? | Konstantin, Rosio | -| 11:25 AM | 45 minutes | Working on your own extension (semi-guided) | Rosio | +| 10:45 AM | 40 minutes | [Debugging: What to do when things go wrong?](./03-materials/04-debugging.md) | Konstantin, Rosio | +| 11:25 AM | 45 minutes | [Working on your own extension (semi-guided)](./03-materials/05-working-on-your-own.md) | Rosio | | 12:10 PM | 20 minutes | Q&A, discussion | All | @@ -28,7 +28,7 @@ | Time | Duration | Topic | Presenter(s) | |-----------|-------------|---------------------------------------------|-------------------| -| 1:30 PM | 45 minutes | Developing extensions with AI assistance | Konstantin, Matt | +| 1:30 PM | 45 minutes | [Developing extensions with AI assistance](./03-materials/06-developing-with-ai.md) | Konstantin, Matt | | 2:15 PM | 45 minutes | Q&A session | All | | 3:00 PM | 30 minutes | โ˜•๏ธ **Coffee break!** | | | 3:30 PM | 90 minutes | "Study hall" | All | diff --git a/03-materials/03-show-and-tell.md b/03-materials/03-show-and-tell.md new file mode 100644 index 00000000..0f0ff127 --- /dev/null +++ b/03-materials/03-show-and-tell.md @@ -0,0 +1,10 @@ +# Show & tell + +:::{hint} Learning objectives + +::: + +:::{tip} Outcome + +::: + diff --git a/03-materials/04-debugging.md b/03-materials/04-debugging.md new file mode 100644 index 00000000..d582ba68 --- /dev/null +++ b/03-materials/04-debugging.md @@ -0,0 +1,10 @@ +# Debugging: What to do when things go wrong? + +:::{hint} Learning objectives + +::: + +:::{tip} Outcome + +::: + diff --git a/03-materials/05-working-on-your-own.md b/03-materials/05-working-on-your-own.md new file mode 100644 index 00000000..12436e3a --- /dev/null +++ b/03-materials/05-working-on-your-own.md @@ -0,0 +1,10 @@ +# Working on your own extension (semi-guided) + +:::{hint} Learning objectives + +::: + +:::{tip} Outcome + +::: + diff --git a/03-materials/06-developing-with-ai.md b/03-materials/06-developing-with-ai.md new file mode 100644 index 00000000..3fdd45d4 --- /dev/null +++ b/03-materials/06-developing-with-ai.md @@ -0,0 +1,10 @@ +# Developing extensions with AI assistance + +:::{hint} Learning objectives + +::: + +:::{tip} Outcome + +::: + From cc8db6b6036e59a6fbb5712a52c1b306b93ca3dd Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sat, 11 Oct 2025 18:06:36 -0700 Subject: [PATCH 002/118] Update 03-show-and-tell.md Co-authored-by: Rosio --- 03-materials/03-show-and-tell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-materials/03-show-and-tell.md b/03-materials/03-show-and-tell.md index 0f0ff127..0abf5e95 100644 --- a/03-materials/03-show-and-tell.md +++ b/03-materials/03-show-and-tell.md @@ -1,4 +1,4 @@ -# Show & tell +# ๐Ÿชฉ Show & tell :::{hint} Learning objectives From f0f5850c6879f5a795eaf81aeebff6fd04784eac Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sat, 11 Oct 2025 18:06:46 -0700 Subject: [PATCH 003/118] Update 04-debugging.md Co-authored-by: Rosio --- 03-materials/04-debugging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-materials/04-debugging.md b/03-materials/04-debugging.md index d582ba68..b24590dd 100644 --- a/03-materials/04-debugging.md +++ b/03-materials/04-debugging.md @@ -1,4 +1,4 @@ -# Debugging: What to do when things go wrong? +# ๐Ÿž Debugging: What to do when things go wrong? :::{hint} Learning objectives From 8d2075010d6850f0e95d58952f334dcc7034b4fa Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sat, 11 Oct 2025 18:10:30 -0700 Subject: [PATCH 004/118] Update 06-developing-with-ai.md Co-authored-by: Rosio --- 03-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-materials/06-developing-with-ai.md b/03-materials/06-developing-with-ai.md index 3fdd45d4..74c87f94 100644 --- a/03-materials/06-developing-with-ai.md +++ b/03-materials/06-developing-with-ai.md @@ -1,4 +1,4 @@ -# Developing extensions with AI assistance +# ๐Ÿค– Developing extensions with AI assistance :::{hint} Learning objectives From 3c93090d0ef902f95846d4a64cbf3b6d2996e925 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sat, 11 Oct 2025 18:10:43 -0700 Subject: [PATCH 005/118] Update 05-working-on-your-own.md Co-authored-by: Rosio --- 03-materials/05-working-on-your-own.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-materials/05-working-on-your-own.md b/03-materials/05-working-on-your-own.md index 12436e3a..4a440c76 100644 --- a/03-materials/05-working-on-your-own.md +++ b/03-materials/05-working-on-your-own.md @@ -1,4 +1,4 @@ -# Working on your own extension (semi-guided) +# ๐Ÿงฉ Working on your own extension (semi-guided) :::{hint} Learning objectives From 258a15d4ffa7f1663f8396866db67984d7b6a301 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:33:21 +0000 Subject: [PATCH 006/118] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 03-materials/03-show-and-tell.md | 1 - 03-materials/04-debugging.md | 1 - 03-materials/05-working-on-your-own.md | 1 - 03-materials/06-developing-with-ai.md | 1 - 4 files changed, 4 deletions(-) diff --git a/03-materials/03-show-and-tell.md b/03-materials/03-show-and-tell.md index 0abf5e95..4982cd6b 100644 --- a/03-materials/03-show-and-tell.md +++ b/03-materials/03-show-and-tell.md @@ -7,4 +7,3 @@ :::{tip} Outcome ::: - diff --git a/03-materials/04-debugging.md b/03-materials/04-debugging.md index b24590dd..d348d68b 100644 --- a/03-materials/04-debugging.md +++ b/03-materials/04-debugging.md @@ -7,4 +7,3 @@ :::{tip} Outcome ::: - diff --git a/03-materials/05-working-on-your-own.md b/03-materials/05-working-on-your-own.md index 4a440c76..ebe2ec35 100644 --- a/03-materials/05-working-on-your-own.md +++ b/03-materials/05-working-on-your-own.md @@ -7,4 +7,3 @@ :::{tip} Outcome ::: - diff --git a/03-materials/06-developing-with-ai.md b/03-materials/06-developing-with-ai.md index 74c87f94..f4ae1d2c 100644 --- a/03-materials/06-developing-with-ai.md +++ b/03-materials/06-developing-with-ai.md @@ -7,4 +7,3 @@ :::{tip} Outcome ::: - From 7693b7478502b982a5358ac2f472e6f8ff3a00f6 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 15 Oct 2025 12:01:47 -0700 Subject: [PATCH 007/118] add learning objectives and outcomes to the AI assistance module --- 04-materials/06-developing-with-ai.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index f4ae1d2c..44b7ec9d 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -1,9 +1,22 @@ # ๐Ÿค– Developing extensions with AI assistance -:::{hint} Learning objectives +::::{hint} Learning objectives +- Use an AI assistant (Cursor, Claude Code, etc.) to explore, understand, and modify JupyterLab extensions by prompting +- Write effective prompts with context, constraints, target files, and acceptance criteria; iterate intentionally +- Point AI to JupyterLab documentation and API reference: provide URLs or local paths, require citations to the exact section, and verify types against your installed version +- Use AI to locate extension points (commands, tokens, services, widgets) and read the relevant APIs +- Generate and refine TypeScript/React UI, commands, settings schemas, and Python server endpoints with AI; review and edit code as needed +- Run build, lint, and tests with AI guidance; interpret errors and request focused fixes +- Generate tests, docs, and PR text with AI aligned to repository conventions +- Use and customize a local AI ruleset (AGENTS.md) in your editor/tool of choice; understand how it works and how to adjust it +- Understand privacy, licensing, and safety considerations when sharing code/data with AI tools +:::: -::: - -:::{tip} Outcome - -::: +::::{tip} Outcome +After this module, you will have: +- Configured a local development environment with AI tools (Cursor, Claude Code, etc.) and the AGENTS.md ruleset +- Either created a new extension from the template with at least one user-visible feature (e.g., a new command plus a small widget or server endpoint), or contributed a meaningful enhancement to an existing extension +- A reproducible prompt checklist/playbook with doc/API citations that you can reuse for future extension work +- Either published your new extension to PyPI and GitHub with a proper README, or opened a pull request to an existing extension repository with a clear description and passing tests +- Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code +:::: From 2c03c42feb4e77e8ab53fc97169bd016966b8a70 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 15 Oct 2025 15:35:06 -0700 Subject: [PATCH 008/118] condense objectives and outcomes --- 04-materials/06-developing-with-ai.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 44b7ec9d..ca7bc4bc 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -1,22 +1,14 @@ # ๐Ÿค– Developing extensions with AI assistance ::::{hint} Learning objectives -- Use an AI assistant (Cursor, Claude Code, etc.) to explore, understand, and modify JupyterLab extensions by prompting -- Write effective prompts with context, constraints, target files, and acceptance criteria; iterate intentionally -- Point AI to JupyterLab documentation and API reference: provide URLs or local paths, require citations to the exact section, and verify types against your installed version -- Use AI to locate extension points (commands, tokens, services, widgets) and read the relevant APIs -- Generate and refine TypeScript/React UI, commands, settings schemas, and Python server endpoints with AI; review and edit code as needed -- Run build, lint, and tests with AI guidance; interpret errors and request focused fixes -- Generate tests, docs, and PR text with AI aligned to repository conventions -- Use and customize a local AI ruleset (AGENTS.md) in your editor/tool of choice; understand how it works and how to adjust it -- Understand privacy, licensing, and safety considerations when sharing code/data with AI tools +- Use an AI assistant (Cursor, Claude Code, etc.) to build and modify JupyterLab extensions by writing effective prompts with context, constraints, and acceptance criteria; point AI to official docs/APIs and require citations +- Generate and refine extension code (TypeScript/React UI, commands, settings schemas, Python endpoints) with AI guidance; run build/lint/tests and interpret errors to request focused fixes +- Configure and customize a local AI ruleset (AGENTS.md) for your development workflow; understand privacy, licensing, and safety considerations when sharing code with AI tools :::: ::::{tip} Outcome After this module, you will have: -- Configured a local development environment with AI tools (Cursor, Claude Code, etc.) and the AGENTS.md ruleset -- Either created a new extension from the template with at least one user-visible feature (e.g., a new command plus a small widget or server endpoint), or contributed a meaningful enhancement to an existing extension -- A reproducible prompt checklist/playbook with doc/API citations that you can reuse for future extension work -- Either published your new extension to PyPI and GitHub with a proper README, or opened a pull request to an existing extension repository with a clear description and passing tests +- Configured a local development environment with AI tools and the AGENTS.md ruleset; created a reproducible prompt checklist/playbook with doc/API citations +- Either created a new extension from the template with at least one user-visible feature, or contributed a meaningful enhancement to an existing extension; shipped it by publishing to PyPI/GitHub or opening a PR with passing tests - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: From e01118c89fd5aca9791b1384751b6b96da675374 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 15 Oct 2025 16:31:58 -0700 Subject: [PATCH 009/118] AI tools intro --- 04-materials/06-developing-with-ai.md | 139 ++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index ca7bc4bc..e255b5d8 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -12,3 +12,142 @@ After this module, you will have: - Either created a new extension from the template with at least one user-visible feature, or contributed a meaningful enhancement to an existing extension; shipped it by publishing to PyPI/GitHub or opening a PR with passing tests - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: + + +## AI-Assisted Development in 2025 + +If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. + +### Understanding LLMs (Large Language Models) + +AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural networks trained on vast amounts of text and code. These models can: +- Understand context from your codebase +- Generate code snippets based on natural language descriptions +- Explain existing code and suggest improvements +- Debug errors by analyzing stack traces and code patterns + +### Where LLMs Live: Deployment Models + +**Frontier Models (Cloud-Hosted):** +- **Examples:** OpenAI GPT-4, Claude 3.5 Sonnet, Google Gemini +- **Deployment:** Run on massive server infrastructure by model providers +- **Access:** Pay-per-token via API keys (typically $0.002โ€“$0.03 per 1K tokens) +- **Pros:** Most capable models, no local compute needed +- **Cons:** Requires internet connection, ongoing costs, data leaves your machine + +**Smaller Models (Cloud or Local):** +- **Examples:** GPT-3.5, Claude Haiku, Llama 3 (8B/70B), Mistral, Qwen +- **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware +- **Pros:** Lower cost or free (if self-hosted), faster responses +- **Cons:** Less capable than frontier models, self-hosting requires GPU resources + +**Open-Source & Open-Weight Models:** +- **Examples:** Llama 3.1, Mistral, Qwen, DeepSeek Coder, StarCoder +- **Licenses:** Vary from fully open (Apache 2.0) to restricted commercial use +- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.ai/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) +- **Pros:** Full control, no API costs, data stays local +- **Cons:** Requires technical setup and adequate hardware (8GB+ VRAM for smaller models) + +### AI IDEs and Tools for Extension Development + +Here are three popular AI coding tools you can use in this workshop, ranging from GUI-first to CLI-native: + +#### 1. **Cursor** (Popular UI-First IDE) +- **What it is:** A fork of VS Code with deep AI integration +- **LLM Options:** + - Built-in models (GPT-4, Claude 3.5) with Cursor subscription (starting $20/month) + - Bring your own API key (OpenAI, Anthropic) + - Can be configured to use local models via OpenAI-compatible APIs +- **Best for:** Developers who want a polished, GUI-driven experience +- **Download:** [cursor.com](https://cursor.com/) + +#### 2. **Cline** (Open-Source VS Code Extension) +- **What it is:** A VS Code extension (formerly Claude Dev) that provides AI-assisted coding +- **LLM Options:** + - Bring your own API key (OpenAI, Anthropic, Google, etc.) + - Supports OpenAI-compatible APIs (e.g., Ollama, LM Studio) + - Fully open-source and community-driven +- **Best for:** Developers who prefer VS Code and want flexibility +- **Install:** Search "Cline" in VS Code extensions or visit [github.com/cline/cline](https://github.com/cline/cline) + +#### 3. **Claude Code** (CLI for Power Users) +- **What it is:** Command-line interface for Claude, optimized for coding workflows +- **LLM Options:** + - Requires Anthropic API key + - Works with Claude 3.5 Sonnet and other Claude models +- **Best for:** CLI warriors who live in the terminal +- **Install:** `pip install claude-code` or check [Anthropic's documentation](https://docs.anthropic.com/) + +:::{tip} Self-Hosting LLMs +If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.ai/) make it easy: + +```bash +# Install Ollama (macOS/Linux) +curl -fsSL https://ollama.ai/install.sh | sh + +# Download and run a coding model +ollama run deepseek-coder:6.7b + +# Use with Cursor/Cline via OpenAI-compatible API +# Point to http://localhost:11434/v1 +``` + +Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. +::: + +### Choosing Your Tool + +For this workshop, we recommend: +- **New to AI coding?** Start with **Cursor** for the smoothest experience +- **Already using VS Code?** Try **Cline** for seamless integration +- **Comfortable with CLI?** **Claude Code** gives you full control + +All three tools work well with the AI-enhanced extension template we'll use next. + +### Further Reading + +- [Simon Willison's blog on AI-assisted programming](https://simonwillison.net/tags/ai-assisted-programming/) โ€” Practical insights from a prolific developer +- ["Not all AI-assisted programming is vibe coding (but vibe coding rocks)"](https://simonwillison.net/2025/Jan/7/vibe-coding/) โ€” Understanding different AI coding styles +- [Ollama Documentation](https://ollama.ai/) โ€” Self-hosting open-source models +- [LM Studio](https://lmstudio.ai/) โ€” GUI for running local LLMs + + +## Getting Started: Clone the AI-Enhanced Extension Template + +For this module, we'll use an enhanced version of the JupyterLab extension template that includes AI-specific configurations and rulesets. + +:::{important} Prerequisites +* `copier`. Install with any of the following options: + * Into an existing environment (don't forget to activate it first): + * `pip install "copier~=9.2" jinja2-time` + * `conda install -c conda-forge "copier~=9.2" jinja2-time` + * Globally: + * `uv tool install --with jinja2-time copier~=9.2` + * `pixi global install "copier~=9.2" --with jinja2-time` +::: + +### Clone and setup the template + +1. Change to the parent directory where you want to work: + + ```bash + cd ~/Projects + ``` + +2. Create an extension repo in a new directory: + + ```bash + mkdir my-jupyterlab-extension + cd my-jupyterlab-extension + git init + ``` + +3. Instantiate the AI-enhanced template: + + ```bash + copier copy --trust https://github.com/orbrx/extension-template-cursor . + ``` + +:::{note} +By JupyterCon 2025, these AI-specific enhancements should be merged into the [official JupyterLab extension template](https://github.com/jupyterlab/extension-template). +::: From 371e1debe5e067bf8bb651b3e025be7d89caf91a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 15 Oct 2025 16:38:34 -0700 Subject: [PATCH 010/118] fix merge conflict --- 04-materials/06-developing-with-ai.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index eaa1a605..e255b5d8 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -1,10 +1,5 @@ # ๐Ÿค– Developing extensions with AI assistance -::::{hint} Learning objectives -- Use an AI assistant (Cursor, Claude Code, etc.) to build and modify JupyterLab extensions by writing effective prompts with context, constraints, and acceptance criteria; point AI to official docs/APIs and require citations -- Generate and refine extension code (TypeScript/React UI, commands, settings schemas, Python endpoints) with AI guidance; run build/lint/tests and interpret errors to request focused fixes -- Configure and customize a local AI ruleset (AGENTS.md) for your development workflow; understand privacy, licensing, and safety considerations when sharing code with AI tools -:::: ::::{hint} Learning objectives - Use an AI assistant (Cursor, Claude Code, etc.) to build and modify JupyterLab extensions by writing effective prompts with context, constraints, and acceptance criteria; point AI to official docs/APIs and require citations - Generate and refine extension code (TypeScript/React UI, commands, settings schemas, Python endpoints) with AI guidance; run build/lint/tests and interpret errors to request focused fixes From aff8a581916c7ea8f6ae9770c2940c0f34b6da40 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Fri, 24 Oct 2025 16:37:43 -0700 Subject: [PATCH 011/118] Add AI development exercises and independent work chapter Chapter 6 (Developing with AI): - Add Exercise 1: Extending image viewer with editing capabilities - Include detailed prompt engineering guidance and examples - Provide two demonstration paths: Cursor AI (IDE) and Claude Code (CLI) - Add debugging workflows for common issues - Include reflection section and challenge extensions Chapter 7 (Independent Work - NEW): - Create new chapter for self-directed exploration time (120 min) - Provide three learning paths: Build from Scratch, Contribute, Explore - Include three tested example projects: * Confetti celebration button (beginner, frontend) * Custom theme extension (beginner, frontend, creative) * CPU monitor widget (intermediate, full-stack) - Add curated list of 18+ extensions for contributions - Include 15+ additional project ideas organized by difficulty - Provide contribution workflow and best practices - Apply Diataxis principles for exploratory learning --- 04-materials/06-developing-with-ai.md | 542 +++++++++++++++++++++++- 04-materials/07-independent-work.md | 567 ++++++++++++++++++++++++++ 2 files changed, 1107 insertions(+), 2 deletions(-) create mode 100644 04-materials/07-independent-work.md diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index f27eb3df..b0c63469 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -29,7 +29,7 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural ### Where LLMs Live: Deployment Models **Frontier Models (Cloud-Hosted):** -- **Examples:** OpenAI GPT-4, Claude 3.5 Sonnet, Google Gemini +- **Examples:** OpenAI's GPT models, Anthropic's Claude models, Google's Gemini models - **Deployment:** Run on massive server infrastructure by model providers - **Access:** Pay-per-token via API keys (typically $0.002โ€“$0.03 per 1K tokens) - **Pros:** Most capable models, no local compute needed @@ -99,7 +99,7 @@ Most AI tools can be "coerced" into using local models by configuring them to po For this workshop, we recommend: - **New to AI coding?** Start with **Cursor** for the smoothest experience -- **Already using VS Code?** Try **Cline** for seamless integration +- **Already using VS Code or can run local models?** Try **Cline** for seamless integration - **Comfortable with CLI?** **Claude Code** gives you full control All three tools work well with the AI-enhanced extension template we'll use next. @@ -151,3 +151,541 @@ For this module, we'll use an enhanced version of the JupyterLab extension templ :::{note} By JupyterCon 2025, these AI-specific enhancements should be merged into the [official JupyterLab extension template](https://github.com/jupyterlab/extension-template). ::: + + +## ๐Ÿ‹๏ธ Exercise 1 (45 minutes): Extending the Image Viewer with AI + +In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. + +### Option 1: Continue with Your Own Extension + +If you completed the anatomy module and want to continue with your extension: + +1. Navigate to your extension directory: + + ```bash + cd ~/Projects/jupytercon2025-extension-workshop + ``` + +2. Ensure your extension is on the final commit from the anatomy module (with the layout restoration feature). + +3. Verify your extension is working: + + ```bash + # Activate your environment + micromamba activate jupytercon2025 + + # Build and start JupyterLab + jlpm build + jupyter lab + ``` + +4. Skip to [Verify AI Configuration](#verify-ai-configuration) below. + +### Option 2: Clone the Finished Extension + +If you'd prefer to start fresh or didn't complete the anatomy module: + +1. Clone the demo repository: + + ```bash + cd ~/Projects + git clone https://github.com/jupytercon/jupytercon2025-developingextensions-demo.git jupytercon2025-ai-workshop + cd jupytercon2025-ai-workshop + ``` + +2. Check out the completed state from the anatomy module: + + ```bash + git checkout exercise-e-step-1 + ``` + +3. Install and verify the extension works: + + ```bash + # Create/activate environment + micromamba create -n jupytercon2025-ai python pip nodejs gh "copier~=9.2" jinja2-time + micromamba activate jupytercon2025-ai + + # Install the extension in development mode + pip install --editable ".[dev,test]" + jupyter labextension develop . --overwrite + jupyter server extension enable jupytercon2025_extension_workshop + + # Build and start JupyterLab + jlpm build + jupyter lab + ``` + +### Verify AI Configuration + +Your extension should include AI-specific configuration files that help guide AI assistants in understanding JupyterLab extension development patterns. + +1. Check for the AI ruleset file: + + ```bash + # Look for either of these files in your extension root + ls -la AGENTS.md CLAUDE.md 2>/dev/null + ``` + + :::{important} + If neither `AGENTS.md` nor `CLAUDE.md` exists, **let an instructor know!** These files contain important context and rules that help AI assistants generate better JupyterLab extension code. + ::: + +2. Review the ruleset file (optional but recommended): + + ```bash + # View the AI ruleset + cat AGENTS.md # or CLAUDE.md + ``` + + These files should be identical and include: + - JupyterLab architecture patterns and best practices + - Common pitfalls to avoid + - TypeScript/React conventions for JupyterLab + - Testing and debugging guidance + - Links to official documentation and APIs + +### Understanding Your Starting Point + +Before we extend the functionality, let's review what the extension currently does: + +**Frontend (TypeScript):** +- `src/index.ts`: Plugin registration, command definitions, launcher/palette integration +- `src/widget.ts`: Main UI widget that displays images and captions +- `src/request.ts`: Utility for communicating with the backend API + +**Backend (Python):** +- `jupytercon2025_extension_workshop/routes.py`: REST API handlers +- `jupytercon2025_extension_workshop/images_and_captions.py`: Image metadata +- `jupytercon2025_extension_workshop/images/`: Image files on disk + +**Current Features:** +- โœ… Displays random images from a curated collection +- โœ… Shows captions for each image +- โœ… Refresh button to load a new random image +- โœ… Layout restoration (widget persists across JupyterLab sessions) + +### Goal: Add Image Editing Capabilities + +We'll use AI to extend this viewer with basic image editing features. This exercise demonstrates: +- How to communicate complex feature requirements to an AI assistant +- How AI can help navigate unfamiliar APIs (PIL/Pillow for image processing) +- The iterative development cycle: prompt โ†’ generate โ†’ test โ†’ refine +- How to debug and fix issues with AI assistance + +### The Prompt + +We'll demonstrate this development process using two different AI tools: +- **Cursor AI** (IDE-integrated approach) +- **Claude Code** (CLI-based approach) + +Here's the prompt we'll use (feel free to adapt it): + +````markdown +Extend this image viewer extension to add image editing capabilities: + +Add editing controls to the widget: +- Buttons for filters: grayscale, sepia, blur, sharpen +- Basic crop functionality (50% crop from center) +- Brightness/contrast adjustments (slider controls) +- Save edited image back to disk + +Use Pillow (PIL) on the backend to process images. The backend should: +- Accept the image filename and editing operation via REST API +- Apply the transformation using appropriate Pillow methods +- Return the processed image to the frontend as base64-encoded data + +The frontend should: +- Update the displayed image immediately after each edit +- Show the current filter/transformation applied +- Allow chaining multiple edits before saving + +Technical requirements: +- Add Pillow to the Python dependencies +- Create a new REST endpoint `/edit-image` in routes.py +- Add filter buttons to the widget toolbar +- Maintain the existing refresh functionality +```` + +:::{tip} Prompt Engineering Tips +**Effective prompts for AI coding assistants include:** + +1. **Context**: What you're working on ("Extend this image viewer extension...") +2. **Specific requirements**: List concrete features ("Buttons for filters: grayscale, sepia...") +3. **Constraints**: Technology choices ("Use Pillow (PIL) on the backend...") +4. **Acceptance criteria**: How to verify success ("Update the displayed image immediately...") + +**Ask AI to cite documentation:** +- "Point to official Pillow API docs for each filter" +- "Link to JupyterLab widget documentation for toolbar buttons" +- "Show me the TypeScript type definitions for REST responses" + +**Request explanations:** +- "Explain why you chose this approach over alternatives" +- "Comment the code explaining the image processing pipeline" +- "What are the tradeoffs of processing images on the backend vs frontend?" +::: + +### Before You Start: Choose Your AI Tool + +Pick **one** of the following paths: + +- ๐Ÿ‘‰ [Path A: Cursor AI (IDE)](#path-a-cursor-ai-ide) - Recommended for beginners +- ๐Ÿ‘‰ [Path B: Claude Code (CLI)](#path-b-claude-code-cli) - For terminal enthusiasts + +--- + +## Path A: Cursor AI (IDE) + +We'll demonstrate the complete development workflow using Cursor AI, showing you how to iterate on the prompt and debug issues. + +:::{note} Live Demo +An instructor will demonstrate this path in real-time. Follow along or take notes to try it yourself afterward. +::: + +### Setup Cursor AI + +1. Download and install Cursor from [cursor.com](https://cursor.com/) + +2. Configure your API key or subscription: + - **Option 1:** Sign up for Cursor subscription ($20/month, includes API access) + - **Option 2:** Bring your own OpenAI or Anthropic API key + - Open Settings (`Cmd/Ctrl + ,`) + - Navigate to "Cursor" โ†’ "API Keys" + - Add your key + +3. Open your extension folder in Cursor: + + ```bash + cursor ~/Projects/jupytercon2025-extension-workshop + ``` + +### Using Cursor to Generate Code + +1. **Open the Cursor Chat panel** (`Cmd/Ctrl + L`) + +2. **Add context files** by clicking the `+` button or dragging files: + - `src/widget.ts` (where UI changes will go) + - `jupytercon2025_extension_workshop/routes.py` (where backend changes will go) + - `AGENTS.md` or `CLAUDE.md` (AI ruleset with JupyterLab patterns) + - `package.json` and `pyproject.toml` (for dependency context) + +3. **Paste the prompt** from above into the chat + +4. **Review the generated code**: + - Cursor will suggest changes across multiple files + - Read through each change carefully + - Look for comments explaining the approach + - Check if dependencies were added correctly + +5. **Accept or modify** the suggestions: + - Click "Accept" to apply all changes + - Or click individual files to review and edit before accepting + +### Testing and Debugging + +1. **Install new Python dependencies** (if Pillow was added): + + ```bash + pip install --editable ".[dev,test]" + ``` + +2. **Rebuild the extension**: + + ```bash + jlpm build + ``` + +3. **Restart JupyterLab** (stop with `Ctrl+C`, then): + + ```bash + jupyter lab + ``` + +4. **Test the new features**: + - Open the image viewer widget + - Try each filter button + - Check the browser console for errors (`F12` or `Cmd+Option+I`) + - Check the terminal running `jupyter lab` for Python errors + +### Common Issues and How to Fix Them with AI + +#### Issue 1: Build Errors + +If `jlpm build` fails: + +1. Copy the error message +2. Return to Cursor chat and ask: + ``` + I got this build error when running `jlpm build`: + + [paste error] + + Please fix the TypeScript issues. + ``` + +#### Issue 2: Runtime Errors + +If you see errors in the JupyterLab UI or browser console: + +1. Take a screenshot or copy the error +2. Ask Cursor: + ``` + The filter buttons don't work. Here's the console error: + + [paste error] + + What's wrong with the API request? Show me the corrected code. + ``` + +#### Issue 3: Backend Errors + +If the Python server throws errors: + +1. Copy the stack trace from the terminal +2. Ask Cursor: + ``` + The /edit-image endpoint is failing with this error: + + [paste traceback] + + Fix the Pillow image processing logic. + ``` + +### Iterating on the Prompt + +After getting basic functionality working, try refining with follow-up prompts: + +``` +The sepia filter looks too strong. Make it more subtle. +``` + +``` +Add undo/redo buttons to revert edits. +``` + +``` +Save the edit history so I can see what filters were applied. +``` + +:::{important} ๐Ÿ’พ **Make Git commits as you go!** +After each successful change: + +```bash +git add . +git commit -m "Add [specific feature] with AI assistance" +``` + +This makes it easy to revert if AI suggests something that breaks your extension. +::: + +--- + +## Path B: Claude Code (CLI) + +For developers who prefer working in the terminal, Claude Code provides a powerful command-line interface for AI-assisted development. + +:::{note} Live Demo +An instructor will demonstrate this path in real-time. Follow along or take notes to try it yourself afterward. +::: + +### Setup Claude Code + +1. Install Claude Code: + + ```bash + pip install claude-code + ``` + +2. Configure your Anthropic API key: + + ```bash + export ANTHROPIC_API_KEY="your-api-key-here" + ``` + + :::{tip} + Add this to your `~/.bashrc` or `~/.zshrc` to persist across sessions: + ```bash + echo 'export ANTHROPIC_API_KEY="your-key"' >> ~/.zshrc + ``` + ::: + +3. Navigate to your extension directory: + + ```bash + cd ~/Projects/jupytercon2025-extension-workshop + ``` + +### Using Claude Code to Generate Code + +1. **Start an interactive session**: + + ```bash + claude-code + ``` + +2. **Provide context** by referencing files in your prompt: + + ``` + I'm working on a JupyterLab extension. Please read these files for context: + - src/widget.ts + - jupytercon2025_extension_workshop/routes.py + - AGENTS.md + - package.json + - pyproject.toml + + [Then paste the main prompt about adding image editing capabilities] + ``` + +3. **Review and apply changes**: + - Claude Code will show diffs for each file + - Type `y` to accept, `n` to skip, or `e` to edit + - Changes are applied directly to your files + +### Testing and Debugging + +Follow the same testing workflow as Path A: + +1. Install dependencies: `pip install --editable ".[dev,test]"` +2. Rebuild: `jlpm build` +3. Restart: `jupyter lab` +4. Test in the browser + +For errors, paste them back into Claude Code: + +``` +I got this error when testing the grayscale filter: + +[paste error] + +Please fix it. +``` + +### Claude Code Tips + +**Run commands without leaving the chat:** + +``` +Can you also run `jlpm build` to verify this compiles? +``` + +**Ask for explanations:** + +``` +Before you change the code, explain how Pillow's ImageFilter.BLUR works +and why you're choosing this approach. +``` + +**Request tests:** + +``` +Generate pytest tests for the new /edit-image endpoint. +``` + +--- + +## Reflection and Next Steps + +After completing this exercise (via either path), take a moment to reflect: + +### What Worked Well? + +- Did AI correctly understand the JupyterLab extension architecture? +- Were the generated changes isolated and focused? +- Did the AI cite relevant documentation (Pillow, JupyterLab APIs)? + +### What Needed Refinement? + +- Did you have to iterate on the prompt to get the right behavior? +- Were there bugs that required manual debugging? +- Did AI make assumptions you had to correct? + +### Key Takeaways + +โœ… **AI excels at:** +- Generating boilerplate code (new endpoints, UI components) +- Navigating unfamiliar APIs (Pillow image processing) +- Explaining code and suggesting improvements +- Fixing syntax and type errors + +โš ๏ธ **AI may struggle with:** +- Complex architectural decisions +- Subtle bugs in asynchronous code +- Optimizing performance +- Understanding project-specific conventions without explicit guidance (that's why `AGENTS.md` helps!) + +:::{tip} Best Practices +1. **Start small**: Get one feature working before adding complexity +2. **Test frequently**: Build and test after each AI-generated change +3. **Commit often**: Use Git to checkpoint working states +4. **Ask questions**: Don't accept code you don't understandโ€”ask AI to explain +5. **Provide feedback**: If AI suggests something wrong, say so and explain why +::: + +### Challenge Extensions (Optional) + +If you finish early or want to continue exploring, try these follow-up prompts: + +1. **Add image rotation:** + ``` + Add 90-degree rotation buttons (clockwise and counter-clockwise) + ``` + +2. **Implement a filter preview:** + ``` + Show a small preview thumbnail for each filter before applying it + ``` + +3. **Add keyboard shortcuts:** + ``` + Add keyboard shortcuts for common filters (g for grayscale, s for sepia) + ``` + +4. **Export functionality:** + ``` + Add a "Download" button that saves the edited image to the user's computer + ``` + +5. **Preset combinations:** + ``` + Add preset buttons that apply multiple filters at once + (e.g., "Vintage" = sepia + slight blur) + ``` + +:::{important} ๐Ÿ’พ **Final Git commit and push!** +```bash +git add . +git commit -m "Complete Exercise 1: Image editing with AI assistance" +git push +``` +::: + + +## What's Next? + +You've now experienced the complete AI-assisted development workflow: +- โœ… Used AI to generate code for new features +- โœ… Debugged issues by iterating on prompts +- โœ… Learned to provide effective context and constraints +- โœ… Understood when to accept AI suggestions vs. when to customize + +### Continuing Your Journey + +The next chapter, {doc}`07-independent-work`, provides **independent exploration time** where you can: + +1. **Build your own extension from scratch** - Using the template and proven project ideas +2. **Contribute to existing extensions** - Give back to the community and learn from production code +3. **Explore and experiment** - Try multiple small projects to deepen your understanding + +Choose the path that interests you most, work at your own pace, and instructors will be available to help when you get stuck. + +:::{tip} Before Moving On +Take a 5-minute break to: +- Stretch and rest your eyes +- Reflect on what you learned in Exercise 1 +- Think about what you'd like to build or explore next +- Grab water or coffee โ˜• + +See you in the next chapter for independent work time! +::: diff --git a/04-materials/07-independent-work.md b/04-materials/07-independent-work.md new file mode 100644 index 00000000..48e5af1c --- /dev/null +++ b/04-materials/07-independent-work.md @@ -0,0 +1,567 @@ +# ๐Ÿš€ 7 - Independent Work: Build or Contribute + +:::{hint} Session Format +This is **independent exploration time** - similar to office hours or a study hall. Work at your own pace on a project that interests you. Instructors are available to help when you get stuck. + +**Duration**: 120 minutes (until the end of the day) +**Format**: Self-directed with instructor support +**Goal**: Apply what you've learned by building something new or contributing to an existing extension +::: + +:::{tip} Choose Your Own Adventure +You have three paths: + +1. **Build from Scratch** - Create a new extension using the template and AI assistance +2. **Contribute to Existing Extensions** - Add features or fix issues in established projects +3. **Explore and Experiment** - Try multiple small ideas to deepen your understanding + +All paths are valuable! Pick what excites you most. +::: + + +## Path 1: Build Your Own Extension from Scratch + +Create something entirely new using the extension template, AI assistance, and the patterns you've learned. + +### Quick Start + +1. Create a new extension from the template: + + ```bash + cd ~/Projects + mkdir my-jupyterlab-extension + cd my-jupyterlab-extension + git init + ``` + +2. Instantiate the template: + + ```bash + copier copy --trust https://github.com/jupyterlab/extension-template . + ``` + +3. Choose your extension type based on your idea: + - **Frontend only**: For UI-only features (widgets, buttons, panels) + - **Frontend + Server**: When you need Python backend logic (data processing, file system access, external APIs) + +4. Install and verify the base extension works: + + ```bash + # Install in development mode + pip install --editable ".[dev,test]" + jupyter labextension develop . --overwrite + + # If you chose frontend + server: + jupyter server extension enable + + # Build and test + jlpm build + jupyter lab + ``` + +### Tested Example Projects + +These three examples have been tested and confirmed working. Pick one that matches your interests - whether you want visual effects, creative customization, or full-stack development. Use them as-is to practice prompt engineering, or as inspiration for your own ideas. + +--- + +#### ๐ŸŽ‰ Confetti Celebration Button + +**Complexity**: Beginner (Frontend only) +**Time**: 15-20 minutes +**What you'll learn**: Status bar integration, DOM manipulation, visual effects + +**The Prompt**: +``` +Add a button in the status bar to celebrate something! It should show confetti +on top of the UI for 3 seconds +``` + +**Expected Results**: +- Status bar shows a celebration button (usually bottom-right) +- Clicking triggers confetti animation overlay +- Animation appears on top of all UI elements +- Confetti clears after ~3 seconds +- Multiple clicks work correctly + +**What to Watch For**: + +If the confetti doesn't appear: +``` +The button is there but I don't see confetti. Check that the z-index +is high enough and the confetti container is properly positioned. +``` + +To customize: +``` +Make the confetti more colorful and add a sound effect when it triggers. +``` + +**Verified Setup**: +- โœ… macOS 15.7, Claude Code (Claude Sonnet 4.5) +- โœ… `conda` environment: Python 3.13, Node.js 22, JupyterLab +- ๐Ÿ“น [Watch the demo](https://www.loom.com/share/2afabea0184045fa868271f9ab0ca083) + +:::{tip} +This is a perfect first project - it's self-contained, purely visual, and you'll immediately see if it works. It teaches you about JupyterLab's status bar API without backend complexity. +::: + +--- + +#### ๐ŸŽจ Custom Theme Extension + +**Complexity**: Beginner (Frontend only) +**Time**: 20-30 minutes +**What you'll learn**: Theme customization, CSS styling, JupyterLab theming system + +**The Prompt**: +``` +Create a theme based on [your favorite movie/show/game/aesthetic] +``` + +**Example**: +``` +Create a theme based on Netflix show KPop Demon Hunters +``` + +**Expected Results**: +- A new theme appears in Settings โ†’ Theme menu +- Theme includes custom colors matching your chosen aesthetic +- JupyterLab interface reflects the theme's visual style +- Theme can be toggled on/off + +**What to Watch For**: + +To add a background image to the main panel: +``` +Can we use this image as a background for main panel? +[paste your image URL] +``` + +Example: +``` +Can we use this image as a background for main panel? +https://www.billboard.com/wp-content/uploads/2025/07/kpop-demon-hunters-billboard-1800.jpg?w=942&h=628&crop=1 +``` + +To refine the styling: +``` +The background image is too bright - make it more subtle with reduced opacity +and add a dark overlay so text is readable. +``` + +To adjust colors: +``` +Update the sidebar and toolbar colors to match the theme's color palette. +Use [specific colors] for accent elements. +``` + +**Tips for Finding Images**: +- Search "[theme name] wallpaper 4k" for high-quality backgrounds +- Look for images with good contrast for text readability +- Consider color palettes - extract 3-5 main colors from your chosen image (AI can help you with it!) +- Free image sources: Unsplash, Pexels, Wallhaven + +:::{tip} +This is a perfect creative project! You get immediate visual feedback, can personalize your JupyterLab environment, and learn how JupyterLab's theming system works. Plus, you'll have a custom theme you actually want to use daily. + +Themes are also great conversation starters - share your theme with other workshop participants! +::: + +--- + +#### ๐Ÿ“Š CPU Monitor Widget + +**Complexity**: Intermediate (Frontend + Server) +**Time**: 30-40 minutes +**What you'll learn**: REST API integration, backend data processing, graceful error handling + +**The Prompt**: +``` +Create a JupyterLab extension that monitors CPU stats: +- Use psutil on the backend to get utilization and temperature data +- Create a REST API endpoint +- Display it in a widget in the main area +- Handle gracefully if some fields aren't available +``` + +**Expected Results**: +- Extension installs `psutil` successfully +- Widget appears in main area when launched +- CPU utilization percentage displays +- Updates automatically (polling every few seconds) +- No errors when temperature data is unavailable +- Widget layout is readable and organized + +**What to Watch For**: + +If `psutil` isn't installed: +``` +I'm getting ModuleNotFoundError for psutil. Update pyproject.toml to +include psutil as a dependency. +``` + +If the widget doesn't update: +``` +The widget shows data once but doesn't update. Add automatic polling +every 2 seconds to refresh the CPU stats. +``` + +To extend it: +``` +Add a graph that shows CPU usage over the last 60 seconds, +and highlight in red when usage is above 80%. +``` + +**Verified Setup**: +- โœ… macOS 15.7, Cursor (Claude Sonnet 4.5 MAX) +- โœ… `conda` environment: Python 3.13, Node.js 22, JupyterLab +- โš ๏ธ Temperature data gracefully handled as N/A on macOS (expected) +- ๐Ÿ“น [Watch the demo](https://www.loom.com/share/9f6d11d537a94a30af7559fd4d80eea2) + +:::{tip} +This teaches you the full stack: backend API design, frontend-backend communication, error handling, and periodic updates. It's a perfect template for any monitoring or dashboard extension. +::: + +--- + +### More Ideas by Difficulty + +Pick an idea that matches your comfort level and interests: + +**Beginner (Frontend Only)**: +1. **Theme switcher dropdown**: Add a quick theme selector to the toolbar for easy switching +2. **Clock widget**: Status bar item showing current time with configurable timezone +3. **Quote of the day**: Main area widget that fetches and displays random quotes +4. **Keyboard shortcut viewer**: Panel showing all available shortcuts +5. **Custom welcome screen**: Override the default launcher with your own design +6. **Pomodoro timer**: Status bar timer for focused work sessions with notifications + +**Intermediate (Frontend + Server)**: +1. **File size analyzer**: Scan workspace directory and show largest files/folders +2. **Git status widget**: Display current branch, uncommitted changes count +3. **Environment inspector**: Show installed packages and Python/Node versions +4. **Todo list with persistence**: Sidebar panel that saves tasks to disk +5. **Markdown preview**: Add support for custom syntax like mermaid diagrams + +**Advanced**: +1. **Collaborative cursor**: Show where teammates are working (requires WebSocket) +2. **AI code review**: Send selected code to an LLM and show suggestions +3. **Performance profiler**: Instrument notebook cells and show execution metrics +4. **Custom file format viewer**: Add support for viewing proprietary file types +5. **Real-time log viewer**: Stream and filter server logs in a widget + + +## Path 2: Contribute to Existing Extensions + +Contributing to established extensions is a great way to learn real-world patterns and give back to the community. + +### Why Contribute? + +- Learn from production-quality code +- Work with maintainers who know JupyterLab well +- Your contribution helps thousands of users +- Build your portfolio and resume +- Connect with the Jupyter community + +### Finding Contribution Opportunities + +#### Extensions Looking for Contributors + +Here are actively-maintained JupyterLab extensions that welcome contributions: + +**Data Visualization & Analysis**: +- [jupyterlab-matplotlib](https://github.com/matplotlib/ipympl) - Interactive matplotlib widgets +- [jupyterlab-plotly](https://github.com/plotly/plotly.py) - Plotly chart integration +- [jupyterlab-bokeh](https://github.com/bokeh/jupyterlab_bokeh) - Bokeh visualization support + +**Development Tools**: +- [jupyterlab-git](https://github.com/jupyterlab/jupyterlab-git) - Git integration UI +- [jupyterlab-lsp](https://github.com/jupyter-lsp/jupyterlab-lsp) - Language Server Protocol support +- [jupyterlab-code-formatter](https://github.com/ryantam626/jupyterlab_code_formatter) - Code formatting tools + +**Collaboration & Sharing**: +- [jupyter-collaboration](https://github.com/jupyterlab/jupyter-collaboration) - Real-time collaboration +- [jupyterlab-commenting](https://github.com/jupyterlab/jupyterlab-commenting) - Add comments to notebooks +- [jupyterlab-drawio](https://github.com/QuantStack/jupyterlab-drawio) - Diagram editor integration + +**File Viewers & Editors**: +- [jupyterlab-myst](https://github.com/executablebooks/jupyterlab-myst) - MyST markdown support +- [jupyterlab-geojson](https://github.com/jupyterlab/jupyter-renderers) - GeoJSON file viewer +- [jupyterlab-fasta](https://github.com/jupyterlab/jupyter-renderers) - Biological sequence viewer + +**Productivity**: +- [jupyterlab-execute-time](https://github.com/deshaw/jupyterlab-execute-time) - Show cell execution times +- [jupyterlab-spellchecker](https://github.com/jupyterlab-contrib/spellchecker) - Spell checking for markdown +- [jupyterlab-variableInspector](https://github.com/lckr/jupyterlab-variableInspector) - Variable explorer + +### How to Find Good First Issues + +1. **Visit the repository** and look for labels: + - `good first issue` + - `help wanted` + - `beginner-friendly` + - `documentation` + +2. **Check the project board** or GitHub Issues for: + - Feature requests that align with your interests + - Bug reports you can reproduce + - Documentation improvements + +3. **Browse recent issues** and look for: + - Questions you can answer + - Problems you've encountered yourself + - Features you wish existed + +### Making Your First Contribution + +1. **Set up the development environment**: + + ```bash + # Fork the repository on GitHub first, then: + git clone https://github.com/YOUR-USERNAME/extension-name.git + cd extension-name + + # Follow the project's CONTRIBUTING.md instructions + # Usually similar to: + pip install --editable ".[dev,test]" + jupyter labextension develop . --overwrite + jlpm build + ``` + +2. **Create a branch** for your work: + + ```bash + git checkout -b fix/issue-123-button-alignment + ``` + +3. **Make your changes**: + - Start small - fix one thing at a time + - Follow the project's code style + - Add or update tests if applicable + - Update documentation + +4. **Test thoroughly**: + + ```bash + # Run the project's test suite + jlpm test + pytest + + # Test manually in JupyterLab + jupyter lab + ``` + +5. **Commit and push**: + + ```bash + git add . + git commit -m "Fix button alignment in toolbar (#123)" + git push -u origin fix/issue-123-button-alignment + ``` + +6. **Open a Pull Request**: + - Go to the original repository on GitHub + - Click "New Pull Request" + - Select your fork and branch + - Describe what you changed and why + - Reference the issue number if applicable + +:::{tip} Using AI for Contributions +AI can help you understand existing code: + +``` +Explain how this widget's lifecycle works - especially the initialize() +and dispose() methods. +``` + +``` +I want to add a new filter option to this panel. Show me where to add +the UI component and how to wire it to the existing filtering logic. +``` + +``` +This extension uses React. Convert this example to use React best practices +while maintaining compatibility with the existing component structure. +``` +::: + +### Contribution Checklist + +Before opening a PR, verify: + +- [ ] Code follows the project's style guide +- [ ] Changes are tested (automated tests and manual testing) +- [ ] Documentation is updated if needed +- [ ] Commit messages are clear and descriptive +- [ ] PR description explains what and why +- [ ] You've read and followed CONTRIBUTING.md +- [ ] Tests pass in CI/CD (after opening PR) + +### When Contributing Gets Stuck + +**If you're blocked**: +- Comment on the issue asking for clarification +- Join the project's community chat (often Gitter or Discord) +- Ask instructors during this session +- It's okay to pause and try something else + +**If maintainers request changes**: +- Don't take it personally - it's about code quality +- Ask questions if feedback is unclear +- Make the requested changes iteratively +- Maintainers are volunteers - be patient and grateful + + +## Path 3: Explore and Experiment + +Not ready to commit to a full project? Try multiple small experiments to deepen your understanding. + +### Micro-Projects (10-15 minutes each) + +These are bite-sized experiments you can complete quickly: + +1. **Change an icon**: Pick an existing JupyterLab command and change its icon +2. **Add a menu item**: Create a new menu entry that opens a URL +3. **Create a notification**: Show a toast notification when JupyterLab loads +4. **Customize the theme**: Add custom CSS to change colors or fonts +5. **Log user actions**: Create a plugin that logs when users run cells + +### Exploration Prompts + +Use these prompts with your AI assistant to explore JupyterLab's capabilities: + +``` +Show me all the different places I can add UI elements in JupyterLab +(toolbar, menu bar, status bar, sidebar, etc.) with minimal code examples. +``` + +``` +Create a simple example of each JupyterLab widget type: MainAreaWidget, +Panel, ToolbarButton, and Dialog. +``` + +``` +Demonstrate how to listen for JupyterLab events: file opened, cell executed, +theme changed, etc. +``` + +``` +Show me how to store and retrieve user settings/preferences for my extension. +``` + +### Reading Code for Learning + +Pick an extension from the list in Path 2 and explore its codebase: + +1. **Start with `package.json`**: What dependencies does it use? +2. **Read `src/index.ts`**: How is the plugin structured? +3. **Find the widgets**: What UI components does it create? +4. **Trace a feature**: Pick one feature and follow it from UI to backend + +**Use AI to understand**: +``` +I'm reading the jupyterlab-git extension. Explain how the git panel +widget is registered and what lifecycle methods it implements. +``` + + +## General Guidance + +### Development Workflow (Reminder) + +The cycle you'll repeat many times: + +1. **Write a clear prompt** - Context, requirements, constraints +2. **Review generated code** - Read it, understand it, don't blindly accept +3. **Build**: `jlpm build` +4. **Test**: Refresh JupyterLab (or restart if backend changed) +5. **Debug**: Browser console and terminal for errors +6. **Iterate**: Refine your prompt based on results +7. **Commit**: Save working states frequently + +### When to Ask for Help + +Don't stay stuck! Ask an instructor if: + +- You've tried the same thing 3-4 times with no progress +- AI suggests something that contradicts JupyterLab patterns +- Build succeeds but feature doesn't appear +- You want guidance on architecture decisions +- You're unsure if your contribution approach is right + +**Asking well gets better help**: +- Share your exact error message +- Show what you've already tried +- Explain what you expected vs. what happened +- Include relevant code snippets + +### Making the Most of This Time + +**Focus on learning, not perfection**: +- Getting stuck and debugging teaches you more than smooth sailing +- Small working features beat ambitious broken ones +- Exploring multiple approaches builds intuition + +**Document your journey**: +- Take notes on what prompts work well +- Save error messages and solutions +- Write down "aha!" moments +- Update your extension's README as you go + +**Connect with others**: +- Share what you're working on with neighbors +- Help each other debug issues +- Show off cool features you've created +- Ask each other questions + +### Wrapping Up Your Work + +Before the session ends, take 10 minutes to: + +1. **Commit your changes**: + ```bash + git add . + git commit -m "Work in progress: [what you accomplished]" + git push + ``` + +2. **Document your progress** in README.md: + - What you built or contributed + - What works and what's still in progress + - Interesting challenges you solved + - Next steps you'd like to take + +3. **Reflect** on what you learned: + - What surprised you about AI-assisted development? + - What JupyterLab concepts clicked for you? + - What would you explore next? + +### After the Workshop + +**To continue developing**: +- Add automated tests (`pytest` for Python, `jest` for TypeScript) +- Set up continuous integration (template includes GitHub Actions) +- Write comprehensive documentation +- Create example notebooks showing your extension in use + +**To share your work**: +- Publish to PyPI: `python -m build && twine upload dist/*` +- Add to the [JupyterLab Extensions list](https://github.com/mauhai/awesome-jupyterlab) +- Write a blog post about your development experience +- Present at a local Jupyter meetup + +**To contribute more**: +- Subscribe to issues on extensions you've contributed to +- Join the [JupyterLab community forum](https://discourse.jupyter.org/) +- Attend Jupyter community calls +- Help others getting started + +:::{tip} The Journey Continues +Building JupyterLab extensions is a skill that develops over time. Every extension you build, every contribution you make, and every bug you debug deepens your understanding. + +The patterns you've learned here - using AI assistance effectively, reading documentation, debugging systematically, and iterating rapidly - apply to all software development, not just JupyterLab. + +Keep building, keep learning, and welcome to the JupyterLab extension developer community! ๐Ÿš€ +::: + From c41c5fb22a70b5bc6c117596b47de7dd86fb8a0e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sun, 26 Oct 2025 10:39:29 -0700 Subject: [PATCH 012/118] Update independent work chapter with enhanced community engagement resources - Replace Gitter/Discord reference with Jupyter Zulip chat for project discussions - Add a tip section highlighting the presence of core developers at JupyterCon 2025, encouraging direct interaction and feedback - Update community contribution suggestions to include Zulip chat, Discourse forum, and community calls for better support and collaboration --- 04-materials/07-independent-work.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/04-materials/07-independent-work.md b/04-materials/07-independent-work.md index 48e5af1c..d28da3bf 100644 --- a/04-materials/07-independent-work.md +++ b/04-materials/07-independent-work.md @@ -402,7 +402,7 @@ Before opening a PR, verify: **If you're blocked**: - Comment on the issue asking for clarification -- Join the project's community chat (often Gitter or Discord) +- Join the [Jupyter Zulip chat](https://jupyter.zulipchat.com) (or project-specific chat if available) - Ask instructors during this session - It's okay to pause and try something else @@ -490,6 +490,17 @@ Don't stay stuck! Ask an instructor if: - You want guidance on architecture decisions - You're unsure if your contribution approach is right +:::{tip} Core Developers Are Here! +Many Jupyter and JupyterLab core developers are in the room and at JupyterCon 2025. This is a unique opportunity to: +- Ask questions about extension development directly from the experts +- Get feedback on your architecture and design decisions +- Learn about upcoming changes and best practices +- Discuss contribution opportunities +- Connect with the people who built the platform you're extending + +Don't be shy - they're here to help and love seeing new contributors! +::: + **Asking well gets better help**: - Share your exact error message - Show what you've already tried @@ -553,8 +564,9 @@ Before the session ends, take 10 minutes to: **To contribute more**: - Subscribe to issues on extensions you've contributed to -- Join the [JupyterLab community forum](https://discourse.jupyter.org/) -- Attend Jupyter community calls +- Join the [Jupyter Zulip chat](https://jupyter.zulipchat.com) for real-time discussions +- Participate in the [Jupyter Discourse forum](https://discourse.jupyter.org/) +- Attend [Jupyter Community Calls](https://discourse.jupyter.org/t/jupyter-community-calendar/2485) - bring your questions or request code review from experienced developers - Help others getting started :::{tip} The Journey Continues From 94dd14d16485224b3da62bb76b6a2ee3e1eb41ef Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sun, 26 Oct 2025 20:00:42 -0700 Subject: [PATCH 013/118] Add one-shot prompt demonstration section emphasizing product manager workflow - Show the power of single-prompt development with complete image editing example - Highlight hidden decisions made without user input (architecture, UI/UX, technical) - Warn about 'Product Manager Trap' of letting AI make all decisions - Provide optional hands-on demo with git rollback instructions - Transition to structured iterative approach for production code --- 04-materials/02-anatomy-of-extensions.md | 3 + 04-materials/06-developing-with-ai.md | 1239 ++++++++++++++++++++-- assets/images/implicit-agents-md.png | Bin 0 -> 107517 bytes 3 files changed, 1152 insertions(+), 90 deletions(-) create mode 100644 assets/images/implicit-agents-md.png diff --git a/04-materials/02-anatomy-of-extensions.md b/04-materials/02-anatomy-of-extensions.md index ea6e8d82..c4906769 100644 --- a/04-materials/02-anatomy-of-extensions.md +++ b/04-materials/02-anatomy-of-extensions.md @@ -47,6 +47,9 @@ This tutorial is inspired by many prior works. photo API is not reliable during the current government shutdown ๐Ÿ˜ญ) * Remove unnecessary use of `conda` in favor of more standard tooling * Structure the activities around in person teaching and exercises + * [Agentic AI Programming for Python Course](https://training.talkpython.fm/courses/agentic-ai-programming-for-python) + by TalkPython['Training'] + * Ideas for teaching AI-assisted coding in 2025 ::: diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index b0c63469..6ab543d3 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -18,6 +18,30 @@ After this module, you will have: If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. +### Setting Expectations: Your AI Partner is Like a Skilled Junior Developer + +Before we dive into tools and techniques, let's set the right mindset for working with AI. + +**Don't expect perfection.** If AI gets 95% of what you need correct, that's incredibleโ€”exactly what you'd get from a talented junior developer given the same instructions. + +**Key mental model:** Think of your AI assistant as a fast, eager junior developer who: +- โœ… Learns quickly and has read tons of documentation (but maybe not the latest stuff) +- โœ… Can scaffold code and explore APIs incredibly fast +- โœ… Sometimes makes mistakes or misunderstands requirements +- โœ… Benefits from clear instructions and iterative feedback +- โœ… Gets better with guidance and context + +**The right mindset:** +- Don't get frustrated by small mistakesโ€”iterate and guide +- Review all generated code (you wouldn't merge a PR without review) +- Ask questions: "Why did you choose this approach?" +- Treat errors as learning opportunities for both you and the AI +- Don't be afraid to roll back to the beginning and start over if AI doubled down on a wrong path + +:::{important} Perfection is Off the Mark +You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. +::: + ### Understanding LLMs (Large Language Models) AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural networks trained on vast amounts of text and code. These models can: @@ -29,35 +53,126 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural ### Where LLMs Live: Deployment Models **Frontier Models (Cloud-Hosted):** -- **Examples:** OpenAI's GPT models, Anthropic's Claude models, Google's Gemini models +- **Examples:** + - **Claude Sonnet 4.5** (Anthropic): Best coding model, $3/$15 per M tokens + - **GPT-5** (OpenAI): Best overall reasoning, $1.25/$10 per M tokens + - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal + - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), $15/$75 per M tokens - **Deployment:** Run on massive server infrastructure by model providers -- **Access:** Pay-per-token via API keys (typically $0.002โ€“$0.03 per 1K tokens) -- **Pros:** Most capable models, no local compute needed +- **Access:** Pay-per-token via API keys +- **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed - **Cons:** Requires internet connection, ongoing costs, data leaves your machine -**Smaller Models (Cloud or Local):** -- **Examples:** GPT-3.5, Claude Haiku, Llama 3 (8B/70B), Mistral, Qwen +**Mid-Tier and Efficient Models (Cloud or Local):** +- **Examples:** Claude Haiku, Qwen3-30B-A3B (approaching GPT-4o performance), Mistral Small 3.2, Llama 3.3-70B - **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware -- **Pros:** Lower cost or free (if self-hosted), faster responses -- **Cons:** Less capable than frontier models, self-hosting requires GPU resources +- **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency +- **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ VRAM) + +**Open-Source & Open-Weight Models (2025 State-of-the-Art):** +- **Highly Recommended for Coding:** + - **Qwen3-235B-A22B** (Apache 2.0): 235B params with 22B active, 262K context, exceptional reasoning + - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware + - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized + - **Qwen3-Coder**: Specialized for code generation tasks + - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) + - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly +- **Other Notable Models:** Llama 3.3-70B (Meta), Mistral Small 3.2, Gemma 3 (Google) +- **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use +- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) +- **Pros:** Full control, no API costs, data stays local, latest Chinese models often outperform Western alternatives +- **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) + +### Understanding AI Coding Tool Categories + +Not all AI coding tools are created equal. Understanding the different categories helps you choose the right tool and set appropriate expectations. + +#### โŒ Chat-Based AI (ChatGPT, Claude web interface) + +**How it works:** +- You paste code snippets โ†’ AI gives you code back +- No context of your full project structure +- Requires manual copy/paste workflow + +**Use cases:** +- โœ… Quick one-off questions ("How do I use async/await in TypeScript?") +- โœ… Explaining error messages +- โœ… Learning new concepts + +**Limitations:** +- โŒ Doesn't understand your codebase +- โŒ Can't run tests or verify solutions +- โŒ Every conversation starts from scratch +- โŒ You're the one integrating fragments into your project + +**Verdict:** Good for learning, frustrating for building features. + +#### โš ๏ธ Autocomplete AI (GitHub Copilot basic mode) + +**How it works:** +- Suggests code as you type (like enhanced IntelliSense) +- Predicts what you'll write next based on context + +**Use cases:** +- โœ… Writing boilerplate code +- โœ… Completing obvious patterns + +**Limitations:** +- โš ๏ธ Often 90% right (which means constantly fighting it) +- โš ๏ธ Interrupts your flow with suggestions +- โš ๏ธ No understanding of "correctness"โ€”just statistical likelihood +- โš ๏ธ Can be distracting or helpful depending on your preference. + +**Verdict:** Some people really prefer this mode (i.e. experienced developers who know when to ignore it, someone who prefers light touch AI interactions), but it can slow down beginners. + +#### โœ… Agentic AI (Cursor, Claude Code, Cline, GitHub Copilot Workspace) + +**How it works:** +- Understands your entire codebase +- Can execute commands (build, test, format) +- Reads documentation and error messages +- Self-corrects when things go wrong +- Works iteratively with you + +**Use cases:** +- โœ… Building complete features from requirements +- โœ… Refactoring across multiple files +- โœ… Debugging with full context +- โœ… Generating tests based on implementation +- โœ… Updating documentation alongside code + +**Key difference:** It doesn't just suggestโ€”it **acts** like a team member with tools. + +**Example workflow:** +``` +You: "Add image filters to this widget. Use Pillow on the backend." + +Agentic AI: +1. Reads your widget.ts to understand structure +2. Checks pyproject.toml for dependencies +3. Adds Pillow to dependencies +4. Creates new endpoint in routes.py +5. Updates widget with filter buttons +6. Runs `jlpm build` to verify TypeScript compiles +7. Reports: "Done. Found one type error, fixed it." +``` -**Open-Source & Open-Weight Models:** -- **Examples:** Llama 3.1, Mistral, Qwen, DeepSeek Coder, StarCoder -- **Licenses:** Vary from fully open (Apache 2.0) to restricted commercial use -- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.ai/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) -- **Pros:** Full control, no API costs, data stays local -- **Cons:** Requires technical setup and adequate hardware (8GB+ VRAM for smaller models) +**Verdict:** **This is what we're using in this workshop.** Agentic AI is a fundamentally different experience from chat or autocomplete. + +:::{tip} Focus on Agentic Tools +If you've tried AI coding before and found it frustrating, chances are you were using chat-based AI or basic autocomplete. The techniques in this workshop are designed for **agentic, tool-using AI** that can understand and operate on your full project. +::: ### AI IDEs and Tools for Extension Development -Here are three popular AI coding tools you can use in this workshop, ranging from GUI-first to CLI-native: +Now that you understand the categories, here are the **agentic AI tools** you can use in this workshop, ranging from GUI-first to CLI-native: #### 1. **Cursor** (Popular UI-First IDE) - **What it is:** A fork of VS Code with deep AI integration - **LLM Options:** - - Built-in models (GPT-4, Claude 3.5) with Cursor subscription (starting $20/month) - - Bring your own API key (OpenAI, Anthropic) - - Can be configured to use local models via OpenAI-compatible APIs + - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) + - Bring your own API key (OpenAI, Anthropic, Google, or other providers) + - Can be configured to use local models (Qwen3-Coder, GLM-4.5, DeepSeek-R1) via OpenAI-compatible APIs - **Best for:** Developers who want a polished, GUI-driven experience - **Download:** [cursor.com](https://cursor.com/) @@ -74,24 +189,42 @@ Here are three popular AI coding tools you can use in this workshop, ranging fro - **What it is:** Command-line interface for Claude, optimized for coding workflows - **LLM Options:** - Requires Anthropic API key - - Works with Claude 3.5 Sonnet and other Claude models -- **Best for:** CLI warriors who live in the terminal -- **Install:** `pip install claude-code` or check [Anthropic's documentation](https://docs.anthropic.com/) + - Works with Claude Sonnet 4.5, Claude 4 Opus, and other Claude models +- **Best for:** CLI warriors who live in the terminal; Simon Willison's primary coding tool +- **Install:** Requires Node.js 18+, then `npm install -g @anthropic/claude-code` :::{tip} Self-Hosting LLMs -If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.ai/) make it easy: +If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: ```bash -# Install Ollama (macOS/Linux) -curl -fsSL https://ollama.ai/install.sh | sh +# Install Ollama +# macOS: Download from https://ollama.com/download/mac +# Windows: Download from https://ollama.com/download/windows +# Linux: +curl -fsSL https://ollama.com/install.sh | sh -# Download and run a coding model -ollama run deepseek-coder:6.7b +# Download and run a recommended coding model +# For powerful machines (24GB+ VRAM): +ollama run qwen3-coder + +# For laptops/consumer hardware (16GB RAM): +ollama run glm-4.5-air + +# For reasoning tasks: +ollama run deepseek-r1 # Use with Cursor/Cline via OpenAI-compatible API # Point to http://localhost:11434/v1 ``` +**Model Selection Guide:** +- **Best for coding on powerful hardware:** Qwen3-235B or GLM-4.5 +- **Best for laptops (48GB RAM):** GLM-4.5 Air (quantized) +- **Best for consumer GPUs (16-24GB):** Qwen3-Coder or DeepSeek-R1-Distill +- **Budget option:** GPT-OSS-20B (runs on 16GB RAM) + +According to Simon Willison's 2025 recommendations, Chinese models (Qwen, GLM) often outperform Western alternatives (Llama, Mistral, Gemma) for coding tasks. + Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. ::: @@ -108,7 +241,10 @@ All three tools work well with the AI-enhanced extension template we'll use next - [Simon Willison's blog on AI-assisted programming](https://simonwillison.net/tags/ai-assisted-programming/) โ€” Practical insights from a prolific developer - ["Not all AI-assisted programming is vibe coding (but vibe coding rocks)"](https://simonwillison.net/2025/Jan/7/vibe-coding/) โ€” Understanding different AI coding styles -- [Ollama Documentation](https://ollama.ai/) โ€” Self-hosting open-source models +- [Claude Sonnet 4.5 review](https://simonwillison.net/2025/Sep/29/claude-sonnet-4-5/) โ€” Simon calls it "the best coding model in the world" +- [Parallel Coding Agents](https://simonwillison.net/2025/Oct/5/parallel-coding-agents/) โ€” Simon's October 2025 workflow with Claude Sonnet 4.5 +- [Simon Willison's Model Recommendations](https://simonwillison.net/2025/Oct/) โ€” Qwen3 and GLM-4.5 for open-source coding; Chinese models often outperform Western alternatives +- [Ollama Documentation](https://ollama.com/) โ€” Self-hosting open-source models - [LM Studio](https://lmstudio.ai/) โ€” GUI for running local LLMs @@ -153,6 +289,306 @@ By JupyterCon 2025, these AI-specific enhancements should be merged into the [of ::: +## ๐Ÿ”ง Exercise 0 (15 minutes): Configure Your AI Development Environment + +Before jumping into code generation, let's set up the "invisible infrastructure" that makes AI assistants work well. This configuration is what makes the difference between mediocre and excellent AI-generated code. + +### Pre-Exercise Checklist + +Make sure you have: + +- [ ] Git repository initialized with clean working tree +- [ ] Your AI tool installed (Cursor, Claude Code, or Cline) +- [ ] Extension template cloned or previous exercise code available +- [ ] Basic understanding: AI is a junior developer (95% accuracy = success!) + +### Part 1: Understand Your AI Rules (AGENTS.md) + +**What are AI Rules?** + +AI Rules (also called Cursor Rules, or system prompts) are instructions that automatically precede every conversation with your AI assistant. They're like permanent coaching that guides the AI's behavior. + +**AGENTS.md: The Emerging Standard** + +In 2025, the AI coding ecosystem converged on **AGENTS.md** as the universal format for agent instructions. Created through collaboration between OpenAI, Google, Sourcegraph, and others, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. + +**Tool Support Status:** + +| Tool | Support | Format | +|------|---------|--------| +| **Cursor** | โœ… Native | AGENTS.md + .cursor/rules/ | +| **GitHub Copilot** | โœ… Native | AGENTS.md (maintains .github/copilot-instructions.md for backward compatibility) | +| **Zed Editor** | โœ… Native | AGENTS.md | +| **Roo Code** | โœ… Native | AGENTS.md | +| **Claude Code** | โš™๏ธ Via symlink | Create: `ln -s AGENTS.md CLAUDE.md` | +| **Gemini CLI** | โš™๏ธ Config | Add to .gemini/settings.json: `{"context": {"fileName": ["AGENTS.md"]}}` | +| **Aider** | โš™๏ธ Config | Add to .aider.conf.yml: `read: AGENTS.md` | +| **Continue.dev** | โŒ Not yet | Use .continue/rules/ | +| **Cline** | โŒ Not yet | Use .clinerules/rules.md | + +**Key advantage:** Single AGENTS.md file works across your entire team regardless of which AI tool they useโ€”no more maintaining separate .cursorrules, CLAUDE.md, and GEMINI.md files. + + +For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. + +#### What's in Your AGENTS.md File + +1. **Check that the file exists:** + + ```bash + ls -la AGENTS.md + cat AGENTS.md # Review the contents + ``` + +2. **Key sections you'll find:** + + **Code Quality Rules:** + - Use structured logging (not `console.log()`) + - Define explicit TypeScript interfaces + - Avoid the `any` type + - Use typeguards over type casts + + **Naming Conventions:** + - Python: `snake_case` for functions, `PascalCase` for classes + - TypeScript: `camelCase` for variables, `PascalCase` for classes + - No mixing styles! + + **Project Structure Guidelines:** + - Frontend code in `src/` + - Backend Python in `/` + - Commands registered in `src/index.ts` + - Routes in `/routes.py` + + **JupyterLab-Specific Patterns:** + - How to register commands + - When to use `ReactWidget` vs `Widget` + - REST API best practices with `ServerConnection` + - State persistence with `IStateDB` + + **Development Workflow:** + - When to run `jlpm build` (TypeScript changes) + - When to restart Jupyter (Python changes) + - How to debug (browser console, terminal logs) + + **Common Pitfalls to Avoid:** + - โŒ Don't use `document.getElementById()` (use JupyterLab APIs) + - โŒ Don't hardcode URLs (use `ServerConnection.makeSettings()`) + - โŒ Don't forget `dispose()` methods (prevents memory leaks) + - โŒ Don't mix `npm` and `jlpm` (use `jlpm` only) + +**Why this matters:** These rules teach AI the JupyterLab patterns **before** it writes any code. Without them, AI might use generic React patterns or wrong APIs. With them, AI generates code that follows JupyterLab conventions from the start. + +3. **Verify AI recognizes the rules:** + + :::{note} No Visual Indicator + Cursor automatically reads and applies AGENTS.md, but there's **no visual indicator** in the interface showing it's active. + [![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) + ::: + + Open Cursor, start a new chat (Ask Mode), and ask: + ``` + What package manager should I use for JupyterLab extensions? + ``` + + AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! + + Try another test: + ``` + What TypeScript conventions should we follow? + ``` + + AI should mention strict mode, camelCase, avoiding `any` type, etc. + + **If AI gives wrong answers** (like suggesting `npm` instead of `jlpm`): + - Restart Cursor + - Make sure AGENTS.md is in your project root (not a subdirectory) + - Check that the file is named exactly `AGENTS.md` (case-sensitive) + +:::{tip} The Magic Behind Good AI Code +When you see AI generate well-structured JupyterLab code in exercises, it's not magic - it's reading your AGENTS.md file! These rules are why AI knows to: +- Use `jlpm` instead of `npm` +- Put commands in `src/index.ts` +- Extend the right base classes +- Follow JupyterLab naming patterns + +Without proper AI context that AGENTS.md provides, we observed AI generating generic code, or using the wrong package manager or thinking too much about source vs. prebuilt extensions. +::: + +If you'd like, you can modify the provided AI rules to include your favorite tools, package managers, and conventions. + +### Part 2: Register Official Documentation + +AI models may not have current JupyterLab documentation in their training data. Let's fix that: + +#### For Cursor Users: + +1. **Open Cursor Settings:** + - macOS: `Cmd + ,` then click "Cursor Settings" (not regular Settings) + - Windows/Linux: `Ctrl + ,` then click "Cursor Settings" + +2. **Navigate to Docs:** + - Settings โ†’ Cursor โ†’ Docs + - Click "Add Docs" + +3. **Add these documentation sources:** + + | Name | URL | + |------|-----| + | JupyterLab | `https://jupyterlab.readthedocs.io/` | + | Lumino | `https://lumino.readthedocs.io/` | + | Jupyter Server | `https://jupyter-server.readthedocs.io/` | + +4. **Add dependency documentation** (if using these libraries): + + | Name | URL | When to add | + |------|-----|-------------| + | Pillow | `https://pillow.readthedocs.io/` | Image processing | + | Pandas | `https://pandas.pydata.org/docs/` | Data manipulation | + | Plotly | `https://plotly.com/python/` | Interactive plots | + +5. **Test documentation integration:** + + In Cursor chat: + ``` + Using @JupyterLab documentation, how do I create a command that opens a new widget? + ``` + + AI should reference actual JupyterLab APIs and link to docs. + +#### For Claude Code / Cline Users: + +Documentation integration requires additional setup: + +- **Claude Code:** Documentation is NOT built into Claude's training. You need to: + - Enable web search in settings, OR + - Use MCP (Model Context Protocol) tools to fetch documentation, OR + - Manually paste relevant documentation into your prompts +- **Cline:** Can be configured to access web docs via settings + +### Part 3: Set Up Your Git Workflow + +**Critical:** Your Git game must be top-shelf when working with AI. + +1. **Enable source control panel** (Cursor/VS Code): + - Open the Source Control view (`Cmd/Ctrl + Shift + G`) + - Keep this panel visible alongside your AI chat + +2. **Understand the Four Safety Levels:** + + ``` + Level 1: Unsaved โ†’ Files on disk (Cmd/Ctrl + Z to undo) + Level 2: Staged โ†’ git add (can unstage) + Level 3: Committed โ†’ git commit (can reset) + Level 4: Pushed โ†’ git push (permanent) + ``` + +3. **Adopt this workflow:** + + ```bash + # After AI generates code: + # 1. Review changes in Source Control panel + + # 2. Stage changes you like: + git add src/widget.ts # stage individual files + + # 3. If AI continues and breaks something: + git restore src/widget.ts # revert to staged version + + # 4. Once everything works: + git commit -m "Add image filter buttons with AI assistance" + + # 5. If you want to undo a commit: + git reset --soft HEAD~1 # keep changes, undo commit + ``` + +4. **Configure Git to show better diffs:** + + ```bash + # Better word-level diffs for TypeScript/Python + git config --global diff.algorithm histogram + + # Show function names in diff headers + git config --global diff.python.xfuncname '^[ \t]*((class|def)[ \t].*)$' + ``` + +:::{danger} Git is Your Safety Net +AI can suggest code that breaks your extension. With frequent commits and staging, you can fearlessly experiment and roll back instantly. **This is not optional**โ€”it's how you work safely with AI. +::: + +### Part 4: Choose Your AI Model Wisely + +**Model selection impacts both quality and cost.** + +#### For Cursor Users: + +1. **Enable model selector:** + - Settings โ†’ Cursor Settings โ†’ General + - Find "Usage Summary" โ†’ Set to "Always" (not "Auto") + - This shows your credit usage at bottom of chat panel + +2. **Choose models strategically:** + + | Task | Recommended Model | Why | + |------|------------------|-----| + | Planning & Reasoning | GPT-5 or Claude Sonnet 4.5 (Thinking) | GPT-5 leads reasoning benchmarks; Claude excellent for extended thinking | + | Coding (Best Overall) | Claude Sonnet 4.5 | "Best coding model in the world" per Simon Willison; 99.29% safety rate | + | Long Coding Sessions | Claude 4 Opus | Sustains focus for 30+ hours, ideal for large refactors and multi-step tasks | + | Speed & Long Context | Gemini 2.5 Pro | 1M token context, sub-second streaming, best latency | + | Quick fixes | Claude Haiku 4.5 or GPT-5 Mini | Faster, cheaper for simple edits and routine tasks | + | Local Development | GLM-4.5 Air, Qwen3-235B, or DeepSeek-R1 | Best open models for self-hosting on consumer hardware | + | Avoid | Auto | Cursor picks cheapest, not best | + +3. **Watch your context usage:** + - Look for percentage in chat (e.g., "23.4%") + - Keep under 50% for best results + - Above 70%? Start a new chat + +4. **Monitor credits:** + - Check `cursor.com/settings` โ†’ Usage + - Typical costs: Planning ($1-2), Implementation ($0.30-0.50) + +:::{tip} Start Big, Optimize Later +For planning and architecture, always use the highest-quality model available: +- **Cloud:** GPT-5 for reasoning, Claude Sonnet 4.5 for coding +- **Self-hosted:** DeepSeek-R1 or Qwen3-235B-A22B + +You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM-4.5 Air) for routine edits, but don't skimp on the thinking phase. +::: + +### Verification: Is Your Environment Ready? + +Run this quick test to verify everything is configured: + +1. **Open your extension in Cursor/AI tool** + +2. **Start a new chat and ask:** + + ``` + Please verify my JupyterLab development setup: + 1. Check that TypeScript is configured correctly + 2. Verify Python package structure + 3. List the coding standards I should follow + 4. Tell me what documentation you have access to + ``` + +3. **AI should respond with:** + - โœ… References to your project structure + - โœ… Mentions rules from AGENTS.md (strict mode, camelCase, etc.) + - โœ… Shows awareness of JupyterLab patterns + - โœ… (If using Cursor) Mentions registered documentation + +4. **If something's missing:** + - Restart Cursor/AI tool + - Check that AGENTS.md or .cursor/rules exist + - Verify documentation was added to Cursor settings + +:::{admonition} You're Ready! +:class: tip +Once AI can reference your project structure, coding rules, and JupyterLab documentation, you're set up for success. This configuration is what separates "AI that generates random code" from "AI that writes code that matches your project's patterns." +::: + +--- + ## ๐Ÿ‹๏ธ Exercise 1 (45 minutes): Extending the Image Viewer with AI In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. @@ -269,18 +705,313 @@ Before we extend the functionality, let's review what the extension currently do ### Goal: Add Image Editing Capabilities We'll use AI to extend this viewer with basic image editing features. This exercise demonstrates: +- **Planning first, coding second** - Let AI architect before implementing - How to communicate complex feature requirements to an AI assistant - How AI can help navigate unfamiliar APIs (PIL/Pillow for image processing) -- The iterative development cycle: prompt โ†’ generate โ†’ test โ†’ refine +- The iterative development cycle: plan โ†’ implement โ†’ test โ†’ refine - How to debug and fix issues with AI assistance +- Managing AI context with fresh chats for new phases + +### Demo: The Power (and Peril) of One-Shot Prompts + +Before we dive into our structured approach, let's witness what modern AI can accomplish with a single, well-crafted prompt. This demonstration shows both the impressive capabilities and important limitations of AI-driven development. + +:::{admonition} One-Shot Prompt Magic +:class: note + +With the right context and a detailed prompt, AI can build complete features in minutes. Here's a prompt that could generate our entire image editing extension: + +``` +Extend this image viewer extension to add image editing capabilities: + +Add editing controls to the widget: +- Buttons for filters: grayscale, sepia, blur, sharpen +- Basic crop functionality (50% crop from center) +- Brightness/contrast adjustments (slider controls) +- Save edited image back to disk + +Use Pillow (PIL) on the backend to process images. The backend should: +- Accept the image filename and editing operation via REST API +- Apply the transformation using appropriate Pillow methods +- Return the processed image to the frontend as base64-encoded data + +The frontend should: +- Update the displayed image immediately after each edit +- Show the current filter/transformation applied +- Allow chaining multiple edits before saving + +Technical requirements: +- Add Pillow to the Python dependencies +- Create a new REST endpoint `/edit-image` in routes.py +- Add filter buttons to the widget toolbar +- Maintain the existing refresh functionality +``` +::: + +#### What Happens with This Prompt? + +When you give this prompt to an AI agent like Cursor or Claude Code, it will typically: + +1. **Analyze your existing codebase** to understand the current structure +2. **Make architectural decisions** about implementation patterns +3. **Generate 200+ lines of code** across multiple files +4. **Update dependencies** in pyproject.toml +5. **Create new endpoints** in your backend +6. **Modify the frontend widget** with new UI controls +7. **Run build commands** to verify everything compiles + +**In about 2-3 minutes**, you could have a fully functional image editor! + +#### The Hidden Cost: Decisions Made Without You + +While impressive, this one-shot approach makes numerous decisions on your behalf: + +**Architecture Decisions:** +- โ“ Should images be processed server-side or client-side? +- โ“ Base64 encoding vs. temporary file URLs? +- โ“ Stateful vs. stateless image processing? +- โ“ Where to store edited images? + +**UI/UX Decisions:** +- โ“ Button placement and styling +- โ“ Slider ranges and defaults +- โ“ Error message presentation +- โ“ Loading state indicators + +**Technical Implementation:** +- โ“ PIL filter parameters (blur radius, sharpen intensity) +- โ“ Image format handling (JPEG quality, PNG transparency) +- โ“ Memory management for large images +- โ“ Caching strategy for processed images + +**Code Quality:** +- โ“ Error handling approach +- โ“ TypeScript type definitions +- โ“ Python type hints +- โ“ Test coverage + +:::{warning} The Product Manager Trap +When you use one-shot prompts, you're essentially saying: "AI, you be the product manager, architect, and developer all at once." + +This works great for prototypes, but in production code, you need to understand and own these decisions. +::: + +#### Try It Yourself (Optional Demo) + +If you want to experience the one-shot approach: + +1. **Create a new branch to experiment:** + ```bash + git checkout -b one-shot-demo + ``` + +2. **Give your AI the one-shot prompt above** + +3. **Watch as it generates the entire feature** (2-3 minutes) + +4. **Test the functionality:** + ```bash + jlpm build + jupyter lab + ``` + +5. **Examine what was created:** + - Look at the code structure + - Notice the architectural choices + - Find at least 3 decisions you might have made differently + +6. **Roll back when done:** + ```bash + git diff # See all the changes + git checkout . # Discard all changes + git checkout main # Return to main branch + git branch -D one-shot-demo # Delete the demo branch + ``` + +#### The Better Way: Structured, Iterative Development + +While one-shot prompts are impressive for demos, professional development requires a more thoughtful approach. We'll now proceed with a structured workflow that: + +1. **Plans before coding** - Understand the architecture first +2. **Implements in phases** - Build incrementally with checkpoints +3. **Reviews each step** - Catch issues early +4. **Maintains control** - You make the key decisions + +This takes longer (45 minutes vs. 3 minutes) but results in: +- โœ… Code you understand and can maintain +- โœ… Architecture that fits your needs +- โœ… Proper error handling and edge cases +- โœ… Learning opportunities at each step + +### Step 1: Create an Implementation Plan (Don't Skip This!) -### The Prompt +**The rise of the Product Manager mindset:** AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. + +Before generating any code, we'll have AI create a phased implementation plan. This: +- โœ… Keeps AI focused and prevents scope creep +- โœ… Gives you a roadmap to refer back to +- โœ… Makes it easy to resume work across sessions +- โœ… Documents architectural decisions + +1. **Create a plans directory:** + + ```bash + mkdir plans + ``` + +2. **Start a new chat in Cursor** and use this prompt: + + ```` + I'm extending a JupyterLab image viewer to add image editing capabilities. + + Please create a detailed implementation plan and save it to plans/image-editing-feature.md + + **Requirements:** + - Add filter buttons (grayscale, sepia, blur, sharpen) + - Use Pillow (PIL) on the backend for processing + - New REST endpoint `/edit-image` for transformations + - Update frontend to display edited images immediately + - Basic crop functionality (50% from center) + - Brightness/contrast sliders + - Save edited image back to disk + + **DO NOT WRITE CODE YET.** Create a phased plan with: + + **Phase 1: MVP** + - Basic filter buttons (grayscale, sepia) + - Backend endpoint scaffolding + - Frontend display of processed images + + **Phase 2: Advanced Filters** + - Blur and sharpen filters + - Crop functionality + - Brightness/contrast adjustments + + **Phase 3: Polish** + - Save functionality + - Undo/redo buttons + - Loading states and error handling + + For each phase, list: + - Specific files to create/modify + - Python/TypeScript dependencies needed + - Testing approach + - Potential issues to watch for + + Save this plan to plans/image-editing-feature.md + ```` + +3. **Review the plan:** + - Open `plans/image-editing-feature.md` + - Read through each phase + - Ask questions if anything is unclear: + ``` + In Phase 1, why did you choose to handle images as base64? + What are the alternatives? + ``` + +4. **Commit the plan:** + + ```bash + git add plans/image-editing-feature.md + git commit -m "Add implementation plan for image editing feature" + ``` + +**Why this matters:** You now have a versioned plan that AI (and you) can reference. As you work through phases, AI will stay focused on the current step. + +:::{tip} Planning Mode vs. File-Based Plans +Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-based plans are: +- โœ… Versioned in Git +- โœ… Synced across machines +- โœ… Reviewable and editable +- โœ… Persistent across sessions + +Always save plans to files: `plans/*.md` +::: + +### Step 2: Implement Phase by Phase + +**Context window management:** Instead of one long chat for everything, start fresh for each phase. + +1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + K`) + +2. **Reference the plan:** + + ``` + We are ready for Phase 1 of @plans/image-editing-feature.md + + Please implement the MVP: basic grayscale and sepia filters with + backend endpoint and frontend display. + ``` + + Note the `@plans/...` syntax tells AI to read that specific file. + +3. **Review changes in Source Control** (keep this panel open!) + +4. **Commit after Phase 1 works:** + + ```bash + git add . + git commit -m "Phase 1: Add basic image filters (grayscale, sepia)" + ``` + +5. **Start ANOTHER fresh chat for Phase 2:** + + ``` + We are ready for Phase 2 of @plans/image-editing-feature.md + + Phase 1 is complete. Now implement advanced filters (blur, sharpen, crop). + ``` + +**Why new chats?** Keeps context window small (<50%), prevents AI confusion, reduces costs. File-based plans allow us to start new chats and still keep the required context. + +### Step 3: Adopt a "Product Manager" Mindset + +**Effective prompts use Product Manager thinking: clear requirements, constraints, and acceptance criteria.** + +Instead of: +> "Add some image filters to the viewer" + +Try this structure: + +````markdown +**User Story:** As a user, I want to apply filters to images before analyzing them. + +**Acceptance Criteria:** +- [ ] Clicking "Grayscale" converts image to grayscale immediately +- [ ] Changes are visible without page reload +- [ ] Original image is preserved (can reset) +- [ ] Multiple filters can be applied in sequence +- [ ] Error messages show if filter fails + +**Technical Requirements:** +- Backend: Use Pillow's `ImageFilter` module +- API: POST to `/api//edit-image` + - Request: `{filename: string, filter_type: string, params?: object}` + - Response: `{success: boolean, image_data: string (base64)}` +- Frontend: Add toolbar buttons using `@jupyterlab/apputils` `Toolbar` +- State: Track edit history in widget state + +**Non-Requirements (for later):** +- Don't implement save functionality yet (Phase 3) +- Don't need undo/redo (Phase 3) +- Don't worry about performance optimization + +**Questions for AI:** +- What's the best way to handle large images? +- Should we cache the base64 data or re-fetch? +- How do we prevent race conditions if user clicks multiple filters quickly? +```` + +This level of detail helps AI give you exactly what you want. + +### The Prompts We'll demonstrate this development process using two different AI tools: - **Cursor AI** (IDE-integrated approach) - **Claude Code** (CLI-based approach) -Here's the prompt we'll use (feel free to adapt it): +Here's the implementation prompt for Phase 1 (feel free to adapt it): ````markdown Extend this image viewer extension to add image editing capabilities: @@ -308,23 +1039,69 @@ Technical requirements: - Maintain the existing refresh functionality ```` -:::{tip} Prompt Engineering Tips -**Effective prompts for AI coding assistants include:** +:::{tip} Prompt Engineering: The Complete Guide +**Effective prompts use the "Product Manager" structure:** + +**1. User Story** (sets context): +``` +As a data scientist, I want to apply image filters before analysis, +so I can preprocess images without leaving JupyterLab. +``` + +**2. Specific Requirements** (what to build): +``` +- Add filter buttons: grayscale, sepia, blur, sharpen +- Filters apply immediately (no reload) +- Multiple filters can be chained +- Original image can be restored +``` + +**3. Technical Constraints** (how to build): +``` +- Backend: Use Pillow (PIL) ImageFilter module +- API: POST /api//edit-image +- Frontend: Toolbar buttons using @jupyterlab/apputils +- State: Track applied filters for undo +``` + +**4. Acceptance Criteria** (how to verify): +``` +- [ ] Clicking "Grayscale" converts image +- [ ] Change is visible within 500ms +- [ ] Console shows no errors +- [ ] Multiple filters can apply sequentially +``` + +**5. Non-Requirements** (scope control): +``` +- Don't implement save yet (Phase 2) +- Don't worry about performance optimization +- Don't need keyboard shortcuts +``` -1. **Context**: What you're working on ("Extend this image viewer extension...") -2. **Specific requirements**: List concrete features ("Buttons for filters: grayscale, sepia...") -3. **Constraints**: Technology choices ("Use Pillow (PIL) on the backend...") -4. **Acceptance criteria**: How to verify success ("Update the displayed image immediately...") +**6. Questions for AI** (get expert input): +``` +- What's the best way to handle large images? +- Should we cache processed images? +- How do we prevent race conditions? +``` **Ask AI to cite documentation:** -- "Point to official Pillow API docs for each filter" -- "Link to JupyterLab widget documentation for toolbar buttons" -- "Show me the TypeScript type definitions for REST responses" - -**Request explanations:** -- "Explain why you chose this approach over alternatives" -- "Comment the code explaining the image processing pipeline" -- "What are the tradeoffs of processing images on the backend vs frontend?" +- "Reference @JupyterLab docs for widget lifecycle" +- "Link to Pillow API for each filter you use" +- "Show TypeScript type definitions from @jupyterlab/services" + +**Request explanations (learn while building):** +- "Explain why you chose this approach over [alternative]" +- "Comment the code explaining the processing pipeline" +- "What are the tradeoffs of backend vs. frontend processing?" + +**Provide feedback loops:** +- "This works, but the sepia is too strongโ€”make it 50% less intense" +- "Good approach, but let's refactor to extract the filter logic" +- "Perfect! Now add tests for edge cases" + +**The more specific you are, the better AI performs.** ::: ### Before You Start: Choose Your AI Tool @@ -411,48 +1188,201 @@ An instructor will demonstrate this path in real-time. Follow along or take note ### Common Issues and How to Fix Them with AI -#### Issue 1: Build Errors +**The debugging workflow:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. + +#### Issue 1: Build Errors (TypeScript) + +**Symptom:** `jlpm build` fails with TypeScript errors -If `jlpm build` fails: +**Instead of manually debugging:** -1. Copy the error message -2. Return to Cursor chat and ask: +1. **Copy the full error output** + +2. **Ask AI to diagnose and fix:** ``` - I got this build error when running `jlpm build`: + The build failed with these TypeScript errors: - [paste error] + [paste error output] - Please fix the TypeScript issues. + Please: + 1. Explain what's causing this + 2. Fix the issues + 3. Explain why the fix works ``` -#### Issue 2: Runtime Errors +**AI will:** +- Identify the root cause (missing import, wrong type, etc.) +- Fix the code across multiple files if needed +- Run `jlpm build` again to verify +- Explain the issue so you learn -If you see errors in the JupyterLab UI or browser console: +**Example error AI handles well:** +``` +error TS2339: Property 'filterType' does not exist on type 'IImageEditRequest' +``` + +AI will add the property to the interface definition automatically. + +#### Issue 2: Runtime Errors (Browser Console) + +**Symptom:** Extension loads but features don't work; errors in browser console + +**Debugging with AI:** -1. Take a screenshot or copy the error -2. Ask Cursor: +1. **Open browser console** (`F12` or `Cmd+Option+I`) + +2. **Copy the error and screenshot the UI state** + +3. **Provide both to AI:** ``` - The filter buttons don't work. Here's the console error: + The filter buttons don't work. Here's what happens: + + [drag screenshot showing the UI] + Console error: [paste error] - What's wrong with the API request? Show me the corrected code. + What's wrong? Fix the issue and explain. ``` -#### Issue 3: Backend Errors +**AI excels at:** +- Simple async/await syntax errors (missing await, incorrect .then() usage) +- API request failures (CORS, wrong endpoint, malformed JSON) +- Event handler issues (button not triggering callback) +- State management bugs (React re-render issues) + +**Pro tip:** Use visual context! Screenshot shows AI exactly what's broken. + +#### Issue 3: Backend Errors (Python Traceback) -If the Python server throws errors: +**Symptom:** Python server crashes or endpoint returns 500 error -1. Copy the stack trace from the terminal -2. Ask Cursor: +**AI debugging workflow:** + +1. **Find the full traceback in terminal** (where `jupyter lab` is running) + +2. **Copy everything** (don't excerptโ€”AI needs full context) + +3. **Paste into chat:** + ``` + The /edit-image endpoint is failing. Full traceback: + + [paste entire traceback] + + The request body is: + {"filename": "image.jpg", "filter_type": "grayscale"} + + Fix the Pillow image processing logic and explain the issue. + ``` + +**AI will:** +- Identify which line causes the crash +- Check if it's a logic error, missing dependency, or type issue +- Fix the code +- Suggest tests to prevent regression + +**Common issues AI catches:** +- File path errors (image not found) +- Pillow API misuse (wrong filter parameters) +- Missing error handling (unhandled exceptions) +- JSON serialization issues (can't encode PIL.Image objects) + +#### Issue 4: Extension Not Loading + +**Symptom:** Extension doesn't appear in JupyterLab after build + +**Systematic AI debugging:** + +``` +My extension isn't loading in JupyterLab. Please check: + +1. Is the extension properly installed? Run: jupyter labextension list +2. Is the server extension enabled? Run: jupyter server extension list +3. Check the browser console for any plugin loading errors +4. Verify the plugin ID in package.json matches what's exported in src/index.ts + +Report findings and fix any issues. +``` + +**AI will systematically check** each step and fix configuration issues. + +#### Issue 5: AI Generates Wrong Code + +**Symptom:** AI's solution doesn't match what you asked for + +**How to redirect AI:** + +โŒ **Don't say:** "This is wrong, try again" + +โœ… **Do say:** +``` +This almost works, but I wanted X instead of Y. + +Current behavior: When I click "Grayscale", nothing happens +Expected behavior: Image should convert to grayscale immediately + +The issue seems to be in the event handler. Can you check if: +- The button's onClick is wired correctly? +- The API request is actually being sent? +- The response is updating the widget state? + +Please fix and explain what was wrong. +``` + +**Provide:** +- Specific behavior delta (current vs. expected) +- Your hypothesis about where the issue is +- Request explanation (AI teaches you debugging) + +#### Issue 6: AI Is Confused or Gives Inconsistent Answers + +**Symptom:** AI suggests code that contradicts what it just wrote, or seems "lost" + +**Likely cause:** Context window is too full (>70%) + +**Solution:** +1. **Start a fresh chat** (`Cmd/Ctrl + K`) +2. **Reference your plan:** ``` - The /edit-image endpoint is failing with this error: + Continuing work on @plans/image-editing-feature.md - [paste traceback] + Phase 1 is complete (committed). I'm debugging an issue where [describe issue]. - Fix the Pillow image processing logic. + The error is: [paste error] ``` +3. **Add relevant files to context:** + - Click `+` in chat + - Add only the files related to the bug (widget.ts, routes.py) + - Don't add the entire project + +**Fresh context = fresh perspective.** + +:::{danger} When AI Debugging Isn't Working +If after 3-4 iterations AI isn't solving the issue: + +1. **Take a break** - Fresh eyes help (you and AI) +2. **Read the code yourself** - You might spot the issue +3. **Search documentation** - AI might be hallucinating an API +4. **Ask a human** - Instructors, teammates, Jupyter Zulip chat +5. **Simplify the problem** - Create minimal reproduction + +AI is powerful but not omniscient. Sometimes manual debugging is faster. +::: + +### AI-Powered Debugging Checklist + +Before asking AI for help, gather: + +- [ ] Full error message (not just first line) +- [ ] Code context (what file, what function) +- [ ] What you were trying to do +- [ ] What happened instead +- [ ] Steps to reproduce +- [ ] Screenshots (for UI issues) + +**More context = better AI debugging.** + ### Iterating on the Prompt After getting basic functionality working, try refining with follow-up prompts: @@ -480,6 +1410,93 @@ git commit -m "Add [specific feature] with AI assistance" This makes it easy to revert if AI suggests something that breaks your extension. ::: +### Advanced: Visual Design with Screenshots + +AI can understand screenshots! This is incredibly powerful for debugging UI issues or communicating design intent. + +#### Debugging UI with Screenshots + +1. **Take a screenshot** of the issue (Cmd+Shift+4 on Mac, Snipping Tool on Windows) + +2. **Drag screenshot into Cursor chat:** + + ``` + The filter button spacing looks off [drag screenshot here] + + Please adjust the CSS so buttons have: + - 8px margin between them + - 16px padding inside each button + - Match JupyterLab's standard button styling from @jupyterlab/ui-components + ``` + +3. **AI will:** + - Analyze the visual layout + - Identify specific CSS issues + - Propose fixes targeting the exact elements + +#### Communicating Design Intent + +You can show AI examples of what you want: + +``` +I want the filter toolbar to look like this [drop screenshot of Photoshop's filter panel] + +Match this visual style: +- Icon-based buttons (not text) +- Tooltips on hover +- Grouped by filter category +- Same color scheme as JupyterLab's toolbar +``` + +AI will interpret the visual design and generate appropriate code. + +#### When to Use Screenshots: + +- โœ… Layout/spacing problems ("buttons are misaligned") +- โœ… Color/theming issues ("doesn't match JupyterLab theme") +- โœ… Showing desired design ("make it look like this") +- โœ… Demonstrating bugs ("the image disappears when I click this") +- โœ… Component placement ("move this above the image") + +#### Example Workflow: + +``` +1. Implement feature +2. Take screenshot of result +3. Drag to AI: "The buttons should be above the image, not below" +4. AI adjusts layout +5. Reload, take new screenshot +6. Drag to AI: "Perfect, but reduce the spacing by half" +``` + +**This iterative visual feedback loop is remarkably effective.** + +### Managing Context and Costs + +As you work through phases, keep an eye on these metrics: + +**Context window percentage** (shown in Cursor chat): +- **< 30%:** Healthy, plenty of room +- **30-50%:** Good, AI still focused +- **50-70%:** Getting crowded, consider new chat soon +- **> 70%:** Start new chat immediately + +**When to start a new chat:** +- โœ… Completed a phase (Phase 1 โ†’ Phase 2) +- โœ… Switching focus areas (backend โ†’ frontend) +- โœ… Context above 50% +- โœ… AI seems confused or gives inconsistent answers +- โœ… Major refactoring complete + +**How to maintain continuity:** +``` +New chat: "We are ready for Phase 2 of @plans/image-editing-feature.md + +Phase 1 is complete and committed. Now implementing advanced filters." +``` + +The plan file provides context without bloating the chat window. + --- ## Path B: Claude Code (CLI) @@ -492,13 +1509,27 @@ An instructor will demonstrate this path in real-time. Follow along or take note ### Setup Claude Code -1. Install Claude Code: +1. Install Node.js (if not already installed): ```bash - pip install claude-code + # macOS (using Homebrew) + brew install node@18 + + # Ubuntu/Debian + curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - + sudo apt install -y nodejs + + # Verify installation + node -v && npm -v + ``` + +2. Install Claude Code: + + ```bash + npm install -g @anthropic/claude-code ``` -2. Configure your Anthropic API key: +3. Configure your Anthropic API key: ```bash export ANTHROPIC_API_KEY="your-api-key-here" @@ -511,7 +1542,7 @@ An instructor will demonstrate this path in real-time. Follow along or take note ``` ::: -3. Navigate to your extension directory: +4. Navigate to your extension directory: ```bash cd ~/Projects/jupytercon2025-extension-workshop @@ -589,38 +1620,65 @@ Generate pytest tests for the new /edit-image endpoint. After completing this exercise (via either path), take a moment to reflect: -### What Worked Well? +### Quick Reflection (Optional) + +Think about these questionsโ€”we'll discuss as a group: -- Did AI correctly understand the JupyterLab extension architecture? -- Were the generated changes isolated and focused? -- Did the AI cite relevant documentation (Pillow, JupyterLab APIs)? +1. **What surprised you most about working with AI?** + - Did it understand JupyterLab patterns better or worse than expected? + - Were there moments where it "just got it" vs. moments where you had to guide it heavily? -### What Needed Refinement? +2. **Which technique was most valuable for you?** + - Planning first with phased implementation? + - Using screenshots for UI debugging? + - Starting fresh chats to manage context? + - The AGENTS.md rules and documentation setup? -- Did you have to iterate on the prompt to get the right behavior? -- Were there bugs that required manual debugging? -- Did AI make assumptions you had to correct? +3. **What would you do differently next time?** + - More detailed planning upfront? + - Smaller phases? + - Different prompting approach? + +:::{note} Group Discussion +We'll share experiences as a group and create a live poll about which techniques resonated most. Your instructor will facilitate this discussion. +::: ### Key Takeaways โœ… **AI excels at:** -- Generating boilerplate code (new endpoints, UI components) -- Navigating unfamiliar APIs (Pillow image processing) -- Explaining code and suggesting improvements -- Fixing syntax and type errors +- **Scaffolding and boilerplate** - New endpoints, UI components, tests +- **Navigating unfamiliar APIs** - Pillow, new JupyterLab features +- **Systematic tasks** - Following patterns, applying transforms +- **Explanation and education** - "Why did you choose this approach?" +- **Self-correction** - Fixing build errors, addressing type issues +- **Iterative refinement** - Adjusting based on your feedback โš ๏ธ **AI may struggle with:** -- Complex architectural decisions -- Subtle bugs in asynchronous code -- Optimizing performance -- Understanding project-specific conventions without explicit guidance (that's why `AGENTS.md` helps!) - -:::{tip} Best Practices -1. **Start small**: Get one feature working before adding complexity -2. **Test frequently**: Build and test after each AI-generated change -3. **Commit often**: Use Git to checkpoint working states -4. **Ask questions**: Don't accept code you don't understandโ€”ask AI to explain -5. **Provide feedback**: If AI suggests something wrong, say so and explain why +- **Complex architectural decisions** - When to use State DB vs. props +- **Complex async bugs** - Race conditions, timing issues, subtle promise chaining errors +- **Performance optimization** - Knowing when code is "fast enough" +- **Project-specific conventions** - Without AGENTS.md guidance +- **Ambiguous requirements** - "Make it better" vs. specific criteria + +๐ŸŽฏ **The sweet spot:** +AI is most effective when you provide: +1. **Clear requirements** (Product Manager mindset) +2. **Project context** (AGENTS.md rules, documentation) +3. **Phased plans** (not trying to do everything at once) +4. **Iterative feedback** (junior developer coaching) +5. **Safety nets** (Git commits, testing) + +:::{tip} Best Practices from This Exercise +1. **Plan before coding**: Written plans keep AI focused across sessions +2. **Start fresh chats**: Keep context <50% for best results +3. **Commit frequently**: After every working phase +4. **Review everything**: You wouldn't merge a PR without review +5. **Ask questions**: "Why did you choose X over Y?" +6. **Provide feedback**: "This works, but let's refactor for clarity" +7. **Use visual tools**: Screenshots communicate design intent effectively +8. **Monitor context**: Watch that percentage, start new chats proactively +9. **Document patterns**: Update AGENTS.md when you establish new conventions +10. **Learn from AI**: Request explanations to understand generated code ::: ### Challenge Extensions (Optional) @@ -661,6 +1719,7 @@ git push ``` ::: +--- ## What's Next? diff --git a/assets/images/implicit-agents-md.png b/assets/images/implicit-agents-md.png new file mode 100644 index 0000000000000000000000000000000000000000..05c53bef6f847ea8de57e28cb18959c91279a270 GIT binary patch literal 107517 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 z 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 Date: Sun, 26 Oct 2025 21:27:14 -0700 Subject: [PATCH 014/118] add whole bunch of details --- .../06-developing-with-ai-advanced.md | 328 ++++++++++++++++++ 04-materials/06-developing-with-ai.md | 33 +- 2 files changed, 339 insertions(+), 22 deletions(-) create mode 100644 04-materials/06-developing-with-ai-advanced.md diff --git a/04-materials/06-developing-with-ai-advanced.md b/04-materials/06-developing-with-ai-advanced.md new file mode 100644 index 00000000..a8165b64 --- /dev/null +++ b/04-materials/06-developing-with-ai-advanced.md @@ -0,0 +1,328 @@ +# ๐Ÿš€ Advanced AI Techniques for JupyterLab Extension Development + +**Note:** This is advanced material for experienced developers who have completed the main AI tutorial. Not required for the workshop. + +## Prerequisites + +Before diving into these advanced techniques, you should have: +- โœ… Completed Exercise 0 (AI configuration) +- โœ… Completed Exercise 1 (Image editing with AI) +- โœ… Comfortable with AGENTS.md rules and documentation setup +- โœ… Experienced the basic AI workflow (plan โ†’ implement โ†’ test) + +## ๐ŸŽฏ Exercise: Build Reusable AI Workflows with Slash Commands + +**Time:** 30-45 minutes + +### What are Slash Commands? + +Slash commands let you create repeatable AI tasks as shortcuts. Instead of typing the same instructions repeatedly, you define them once and invoke with `/command-name`. + +**Where they live:** +- Project-specific: `.cursor/commands/*.md` +- Global (all projects): `~/.cursor/commands/*.md` + +**When to use them:** +- โœ… You find yourself giving the same instructions to AI repeatedly +- โœ… You want to standardize code review processes +- โœ… You're working with a team and want consistent AI interactions +- โœ… You want to build a library of debugging/testing workflows + +**When NOT to use them:** +- โŒ During initial learning (adds complexity) +- โŒ For one-off tasks +- โŒ When you're still figuring out what prompts work + +### Part 1: Create Your First Slash Command - Extension Health Check + +**Goal:** Build a command that systematically checks extension configuration. + +1. **Create the commands directory:** + + ```bash + mkdir -p .cursor/commands + ``` + +2. **Create `.cursor/commands/extension-check.md`:** + + ````markdown + --- + name: extension-check + description: Verify JupyterLab extension is properly configured and built + --- + + # JupyterLab Extension Health Check + + Review this JupyterLab extension and verify: + + ## 1. Build Configuration + - `package.json` has proper `@jupyterlab/builder` setup + - `pyproject.toml` includes correct metadata and dependencies + - `tsconfig.json` is configured with strict mode + - All required dev dependencies are listed + + ## 2. Development Install Status + - Python package is editable-installed (`pip install -e`) + - Extension is linked with `jupyter labextension develop` + - Server extension is enabled (if applicable) + - All dependencies are installed + + ## 3. Code Quality + - Run `jlpm build` and report any TypeScript errors + - Check for Python linting issues + - Look for console warnings in code + - Verify proper error handling in async functions + + ## 4. Extension Registration + - Plugin properly exports `JupyterFrontEndPlugin` + - All commands are registered in `src/index.ts` + - Server extension handlers are registered (if backend exists) + - Widget lifecycle methods implemented (dispose, etc.) + + ## 5. Testing + - Test files exist and follow naming conventions + - Run tests if possible and report results + - Check test coverage if available + + Provide a summary with: + - โœ… What's working well + - โš ๏ธ Warnings or potential issues + - โŒ Critical problems that need fixing + - ๐Ÿ“ Recommendations for improvement + + Do not make changes, just report findings. + ```` + +3. **Test the command:** + + In Cursor chat, type: + ``` + /extension-check + ``` + + AI will systematically verify your extension without you typing the whole checklist. + +4. **Commit it:** + + ```bash + git add .cursor/commands/extension-check.md + git commit -m "Add extension health check slash command" + ``` + +### Part 2: Build a Test Review Command + +Create `.cursor/commands/test-review.md`: + +````markdown +--- +name: test-review +description: Review test coverage and quality +--- + +# Test Suite Review + +Act as a software quality engineer reviewing our test suite: + +1. **Coverage Analysis:** + - Run `jlpm test` (if tests exist) + - Identify untested code paths + - Highlight critical features without tests + +2. **Test Quality:** + - Check for proper mocking of external dependencies + - Verify both success and error paths are tested + - Look for flaky tests (time-dependent, order-dependent) + - Ensure tests are independent (can run in any order) + +3. **Best Practices:** + - Tests follow naming conventions (`test_*.py`, `*.spec.ts`) + - Test data is isolated (no shared state) + - Assertions are specific (not just "truthy" checks) + - Edge cases are covered (empty input, null, undefined) + +4. **Recommendations:** + - List top 3 areas where tests would add most value + - Suggest specific test cases to add + - Identify over-tested areas (diminishing returns) + +Provide actionable summary. Do not write tests, just analyze. +```` + +**Usage:** +``` +/test-review + +Then ask follow-up: "What's our biggest testing gap?" +``` + +### Part 3: Create a Debug Helper Command + +Create `.cursor/commands/debug-help.md`: + +````markdown +--- +name: debug-help +description: Analyze build or runtime errors systematically +--- + +# Debug Assistant + +Help me debug an issue. Please: + +1. **Understand the Error:** + - What type of error is this? (TypeScript, Python, Runtime, etc.) + - What's the root cause vs. symptoms? + - What part of the system is affected? + +2. **Analyze Context:** + - Check related code for obvious issues + - Look for recent changes that might have caused this + - Identify missing imports, type errors, or async issues + +3. **Propose Solutions:** + - Suggest 2-3 possible fixes, ranked by likelihood + - Explain trade-offs of each approach + - Cite relevant documentation if applicable + +4. **Prevention:** + - How can we prevent this in the future? + - Should we add tests? Linting rules? Type guards? + +After analysis, ask if I want you to implement the fix. +```` + +**Usage:** +``` +/debug-help + +[Paste error message] + +AI will systematically diagnose and propose fixes +``` + +### Part 4: API Usage Checker + +Create `.cursor/commands/api-usage.md`: + +````markdown +--- +name: api-usage +description: Verify correct use of JupyterLab APIs +--- + +# JupyterLab API Usage Review + +Review the code for proper use of JupyterLab APIs: + +1. **Widget Lifecycle:** + - Widgets properly dispose resources in `dispose()` + - No memory leaks from event listeners + - State is saved/restored correctly + +2. **Command Registry:** + - Commands use unique IDs (namespaced) + - Command handlers return Promises where appropriate + - Commands are properly toggled/enabled/disabled + +3. **REST API Calls:** + - Using `ServerConnection.makeSettings()` not hardcoded URLs + - Proper error handling on all requests + - Request/response types are defined + - CORS is handled correctly + +4. **State Management:** + - Using `IStateDB` for persistence (not localStorage) + - State keys are unique and namespaced + - State is cleaned up when widget is disposed + +5. **Styling:** + - Using JupyterLab CSS variables (not hardcoded colors) + - Proper use of lumino CSS classes + - Dark mode compatibility + +Cite official JupyterLab documentation for recommendations. +Report findings without making changes. +```` + +### Part 5: Composing Commands + +You can use multiple slash commands in one chat: + +``` +/extension-check + +[Review results] + +/test-review + +[Review test analysis] + +Based on both reviews, what are the top 3 priorities to fix? +``` + +This creates a systematic code review workflow. + +### Part 6: Share with Your Team + +**Commit all commands:** + +```bash +git add .cursor/commands/ +git commit -m "Add reusable AI slash commands for extension development" +git push +``` + +Now your entire team can use these workflows! + +### Advanced: Build Your Command Library + +Over time, create slash commands for: + +- `/refactor-suggestion` - Identify code smells and suggest refactoring +- `/performance-review` - Find optimization opportunities +- `/security-check` - Look for security vulnerabilities +- `/accessibility-audit` - Check UI accessibility compliance +- `/docs-update` - Update README/docstrings after code changes +- `/breaking-changes` - Analyze API changes for backward compatibility + +These become your personal AI-powered code review team. + +### Best Practices for Slash Commands + +**DO:** +- โœ… Start with commands you use repeatedly +- โœ… Make them focused (one clear purpose) +- โœ… Include clear success criteria +- โœ… Ask AI to report without changing (for review commands) +- โœ… Share with team via Git + +**DON'T:** +- โŒ Create commands before understanding the workflow +- โŒ Make overly complex commands (break into smaller ones) +- โŒ Duplicate what AI already knows (use AGENTS.md instead) +- โŒ Create commands for one-off tasks + +## Key Takeaways + +โœ… **You've learned to:** +- Create reusable AI workflows with slash commands +- Build a systematic code review process +- Share AI workflows across your team +- Compose multiple commands for comprehensive reviews + +โš ๏ธ **Remember:** +- Slash commands are for repeated workflows, not initial learning +- Start simple (extension-check) before building complex commands +- Commands should complement AGENTS.md rules, not duplicate them +- The best commands emerge from real repeated needs + +๐ŸŽฏ **When to use this:** +- You're working on multiple JupyterLab extensions +- You want consistent code review standards +- You're onboarding new team members +- You've established patterns you want to enforce + +--- + +**This advanced material is not required for the workshop. It's provided for reference when you're ready to optimize your AI workflows in production environments.** + diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 6ab543d3..61fedc1e 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -712,7 +712,7 @@ We'll use AI to extend this viewer with basic image editing features. This exerc - How to debug and fix issues with AI assistance - Managing AI context with fresh chats for new phases -### Demo: The Power (and Peril) of One-Shot Prompts +### Step 1: The Power (and Peril) of One-Shot Prompts Before we dive into our structured approach, let's witness what modern AI can accomplish with a single, well-crafted prompt. This demonstration shows both the impressive capabilities and important limitations of AI-driven development. @@ -760,7 +760,7 @@ When you give this prompt to an AI agent like Cursor or Claude Code, it will typ 6. **Modify the frontend widget** with new UI controls 7. **Run build commands** to verify everything compiles -**In about 2-3 minutes**, you could have a fully functional image editor! +**In about 2-3 minutes**, you will have a fully functional image editor! #### The Hidden Cost: Decisions Made Without You @@ -800,33 +800,22 @@ This works great for prototypes, but in production code, you need to understand If you want to experience the one-shot approach: -1. **Create a new branch to experiment:** - ```bash - git checkout -b one-shot-demo - ``` - -2. **Give your AI the one-shot prompt above** +1. **Give your AI the one-shot prompt above** -3. **Watch as it generates the entire feature** (2-3 minutes) +2. **Watch as it generates the entire feature** (2-3 minutes) -4. **Test the functionality:** +3. **Test the functionality:** ```bash jlpm build jupyter lab ``` -5. **Examine what was created:** +4. **Examine what was created:** - Look at the code structure - Notice the architectural choices - Find at least 3 decisions you might have made differently -6. **Roll back when done:** - ```bash - git diff # See all the changes - git checkout . # Discard all changes - git checkout main # Return to main branch - git branch -D one-shot-demo # Delete the demo branch - ``` +5. **Roll back when done:** #### The Better Way: Structured, Iterative Development @@ -837,13 +826,13 @@ While one-shot prompts are impressive for demos, professional development requir 3. **Reviews each step** - Catch issues early 4. **Maintains control** - You make the key decisions -This takes longer (45 minutes vs. 3 minutes) but results in: +This takes longer but results in: - โœ… Code you understand and can maintain - โœ… Architecture that fits your needs - โœ… Proper error handling and edge cases - โœ… Learning opportunities at each step -### Step 1: Create an Implementation Plan (Don't Skip This!) +### Step 2: Create an Implementation Plan **The rise of the Product Manager mindset:** AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. @@ -929,7 +918,7 @@ Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-ba Always save plans to files: `plans/*.md` ::: -### Step 2: Implement Phase by Phase +### Step 3: Implement Phase by Phase **Context window management:** Instead of one long chat for everything, start fresh for each phase. @@ -965,7 +954,7 @@ Always save plans to files: `plans/*.md` **Why new chats?** Keeps context window small (<50%), prevents AI confusion, reduces costs. File-based plans allow us to start new chats and still keep the required context. -### Step 3: Adopt a "Product Manager" Mindset +### Step 4: Adopt a "Product Manager" Mindset **Effective prompts use Product Manager thinking: clear requirements, constraints, and acceptance criteria.** From 576d9de93048261470a290c4740aa3966a705396 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sun, 26 Oct 2025 21:31:58 -0700 Subject: [PATCH 015/118] fix link --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 61fedc1e..742b39ac 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -240,7 +240,7 @@ All three tools work well with the AI-enhanced extension template we'll use next ### Further Reading - [Simon Willison's blog on AI-assisted programming](https://simonwillison.net/tags/ai-assisted-programming/) โ€” Practical insights from a prolific developer -- ["Not all AI-assisted programming is vibe coding (but vibe coding rocks)"](https://simonwillison.net/2025/Jan/7/vibe-coding/) โ€” Understanding different AI coding styles +- ["Not all AI-assisted programming is vibe coding (but vibe coding rocks)"](https://simonwillison.net/2025/Mar/19/vibe-coding/) โ€” Understanding different AI coding styles - [Claude Sonnet 4.5 review](https://simonwillison.net/2025/Sep/29/claude-sonnet-4-5/) โ€” Simon calls it "the best coding model in the world" - [Parallel Coding Agents](https://simonwillison.net/2025/Oct/5/parallel-coding-agents/) โ€” Simon's October 2025 workflow with Claude Sonnet 4.5 - [Simon Willison's Model Recommendations](https://simonwillison.net/2025/Oct/) โ€” Qwen3 and GLM-4.5 for open-source coding; Chinese models often outperform Western alternatives From c2d2c042b933509df9c49d8595463e5b8f1b2b9d Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 10:29:55 -0700 Subject: [PATCH 016/118] add outline of exercises and get ready for a short review --- .../06-developing-with-ai-advanced.md | 328 ---- 04-materials/06-developing-with-ai.md | 1399 +---------------- 04-materials/07-independent-work.md | 33 +- 3 files changed, 43 insertions(+), 1717 deletions(-) delete mode 100644 04-materials/06-developing-with-ai-advanced.md diff --git a/04-materials/06-developing-with-ai-advanced.md b/04-materials/06-developing-with-ai-advanced.md deleted file mode 100644 index a8165b64..00000000 --- a/04-materials/06-developing-with-ai-advanced.md +++ /dev/null @@ -1,328 +0,0 @@ -# ๐Ÿš€ Advanced AI Techniques for JupyterLab Extension Development - -**Note:** This is advanced material for experienced developers who have completed the main AI tutorial. Not required for the workshop. - -## Prerequisites - -Before diving into these advanced techniques, you should have: -- โœ… Completed Exercise 0 (AI configuration) -- โœ… Completed Exercise 1 (Image editing with AI) -- โœ… Comfortable with AGENTS.md rules and documentation setup -- โœ… Experienced the basic AI workflow (plan โ†’ implement โ†’ test) - -## ๐ŸŽฏ Exercise: Build Reusable AI Workflows with Slash Commands - -**Time:** 30-45 minutes - -### What are Slash Commands? - -Slash commands let you create repeatable AI tasks as shortcuts. Instead of typing the same instructions repeatedly, you define them once and invoke with `/command-name`. - -**Where they live:** -- Project-specific: `.cursor/commands/*.md` -- Global (all projects): `~/.cursor/commands/*.md` - -**When to use them:** -- โœ… You find yourself giving the same instructions to AI repeatedly -- โœ… You want to standardize code review processes -- โœ… You're working with a team and want consistent AI interactions -- โœ… You want to build a library of debugging/testing workflows - -**When NOT to use them:** -- โŒ During initial learning (adds complexity) -- โŒ For one-off tasks -- โŒ When you're still figuring out what prompts work - -### Part 1: Create Your First Slash Command - Extension Health Check - -**Goal:** Build a command that systematically checks extension configuration. - -1. **Create the commands directory:** - - ```bash - mkdir -p .cursor/commands - ``` - -2. **Create `.cursor/commands/extension-check.md`:** - - ````markdown - --- - name: extension-check - description: Verify JupyterLab extension is properly configured and built - --- - - # JupyterLab Extension Health Check - - Review this JupyterLab extension and verify: - - ## 1. Build Configuration - - `package.json` has proper `@jupyterlab/builder` setup - - `pyproject.toml` includes correct metadata and dependencies - - `tsconfig.json` is configured with strict mode - - All required dev dependencies are listed - - ## 2. Development Install Status - - Python package is editable-installed (`pip install -e`) - - Extension is linked with `jupyter labextension develop` - - Server extension is enabled (if applicable) - - All dependencies are installed - - ## 3. Code Quality - - Run `jlpm build` and report any TypeScript errors - - Check for Python linting issues - - Look for console warnings in code - - Verify proper error handling in async functions - - ## 4. Extension Registration - - Plugin properly exports `JupyterFrontEndPlugin` - - All commands are registered in `src/index.ts` - - Server extension handlers are registered (if backend exists) - - Widget lifecycle methods implemented (dispose, etc.) - - ## 5. Testing - - Test files exist and follow naming conventions - - Run tests if possible and report results - - Check test coverage if available - - Provide a summary with: - - โœ… What's working well - - โš ๏ธ Warnings or potential issues - - โŒ Critical problems that need fixing - - ๐Ÿ“ Recommendations for improvement - - Do not make changes, just report findings. - ```` - -3. **Test the command:** - - In Cursor chat, type: - ``` - /extension-check - ``` - - AI will systematically verify your extension without you typing the whole checklist. - -4. **Commit it:** - - ```bash - git add .cursor/commands/extension-check.md - git commit -m "Add extension health check slash command" - ``` - -### Part 2: Build a Test Review Command - -Create `.cursor/commands/test-review.md`: - -````markdown ---- -name: test-review -description: Review test coverage and quality ---- - -# Test Suite Review - -Act as a software quality engineer reviewing our test suite: - -1. **Coverage Analysis:** - - Run `jlpm test` (if tests exist) - - Identify untested code paths - - Highlight critical features without tests - -2. **Test Quality:** - - Check for proper mocking of external dependencies - - Verify both success and error paths are tested - - Look for flaky tests (time-dependent, order-dependent) - - Ensure tests are independent (can run in any order) - -3. **Best Practices:** - - Tests follow naming conventions (`test_*.py`, `*.spec.ts`) - - Test data is isolated (no shared state) - - Assertions are specific (not just "truthy" checks) - - Edge cases are covered (empty input, null, undefined) - -4. **Recommendations:** - - List top 3 areas where tests would add most value - - Suggest specific test cases to add - - Identify over-tested areas (diminishing returns) - -Provide actionable summary. Do not write tests, just analyze. -```` - -**Usage:** -``` -/test-review - -Then ask follow-up: "What's our biggest testing gap?" -``` - -### Part 3: Create a Debug Helper Command - -Create `.cursor/commands/debug-help.md`: - -````markdown ---- -name: debug-help -description: Analyze build or runtime errors systematically ---- - -# Debug Assistant - -Help me debug an issue. Please: - -1. **Understand the Error:** - - What type of error is this? (TypeScript, Python, Runtime, etc.) - - What's the root cause vs. symptoms? - - What part of the system is affected? - -2. **Analyze Context:** - - Check related code for obvious issues - - Look for recent changes that might have caused this - - Identify missing imports, type errors, or async issues - -3. **Propose Solutions:** - - Suggest 2-3 possible fixes, ranked by likelihood - - Explain trade-offs of each approach - - Cite relevant documentation if applicable - -4. **Prevention:** - - How can we prevent this in the future? - - Should we add tests? Linting rules? Type guards? - -After analysis, ask if I want you to implement the fix. -```` - -**Usage:** -``` -/debug-help - -[Paste error message] - -AI will systematically diagnose and propose fixes -``` - -### Part 4: API Usage Checker - -Create `.cursor/commands/api-usage.md`: - -````markdown ---- -name: api-usage -description: Verify correct use of JupyterLab APIs ---- - -# JupyterLab API Usage Review - -Review the code for proper use of JupyterLab APIs: - -1. **Widget Lifecycle:** - - Widgets properly dispose resources in `dispose()` - - No memory leaks from event listeners - - State is saved/restored correctly - -2. **Command Registry:** - - Commands use unique IDs (namespaced) - - Command handlers return Promises where appropriate - - Commands are properly toggled/enabled/disabled - -3. **REST API Calls:** - - Using `ServerConnection.makeSettings()` not hardcoded URLs - - Proper error handling on all requests - - Request/response types are defined - - CORS is handled correctly - -4. **State Management:** - - Using `IStateDB` for persistence (not localStorage) - - State keys are unique and namespaced - - State is cleaned up when widget is disposed - -5. **Styling:** - - Using JupyterLab CSS variables (not hardcoded colors) - - Proper use of lumino CSS classes - - Dark mode compatibility - -Cite official JupyterLab documentation for recommendations. -Report findings without making changes. -```` - -### Part 5: Composing Commands - -You can use multiple slash commands in one chat: - -``` -/extension-check - -[Review results] - -/test-review - -[Review test analysis] - -Based on both reviews, what are the top 3 priorities to fix? -``` - -This creates a systematic code review workflow. - -### Part 6: Share with Your Team - -**Commit all commands:** - -```bash -git add .cursor/commands/ -git commit -m "Add reusable AI slash commands for extension development" -git push -``` - -Now your entire team can use these workflows! - -### Advanced: Build Your Command Library - -Over time, create slash commands for: - -- `/refactor-suggestion` - Identify code smells and suggest refactoring -- `/performance-review` - Find optimization opportunities -- `/security-check` - Look for security vulnerabilities -- `/accessibility-audit` - Check UI accessibility compliance -- `/docs-update` - Update README/docstrings after code changes -- `/breaking-changes` - Analyze API changes for backward compatibility - -These become your personal AI-powered code review team. - -### Best Practices for Slash Commands - -**DO:** -- โœ… Start with commands you use repeatedly -- โœ… Make them focused (one clear purpose) -- โœ… Include clear success criteria -- โœ… Ask AI to report without changing (for review commands) -- โœ… Share with team via Git - -**DON'T:** -- โŒ Create commands before understanding the workflow -- โŒ Make overly complex commands (break into smaller ones) -- โŒ Duplicate what AI already knows (use AGENTS.md instead) -- โŒ Create commands for one-off tasks - -## Key Takeaways - -โœ… **You've learned to:** -- Create reusable AI workflows with slash commands -- Build a systematic code review process -- Share AI workflows across your team -- Compose multiple commands for comprehensive reviews - -โš ๏ธ **Remember:** -- Slash commands are for repeated workflows, not initial learning -- Start simple (extension-check) before building complex commands -- Commands should complement AGENTS.md rules, not duplicate them -- The best commands emerge from real repeated needs - -๐ŸŽฏ **When to use this:** -- You're working on multiple JupyterLab extensions -- You want consistent code review standards -- You're onboarding new team members -- You've established patterns you want to enforce - ---- - -**This advanced material is not required for the workshop. It's provided for reference when you're ready to optimize your AI workflows in production environments.** - diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 742b39ac..6a4f8602 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -223,22 +223,17 @@ ollama run deepseek-r1 - **Best for consumer GPUs (16-24GB):** Qwen3-Coder or DeepSeek-R1-Distill - **Budget option:** GPT-OSS-20B (runs on 16GB RAM) -According to Simon Willison's 2025 recommendations, Chinese models (Qwen, GLM) often outperform Western alternatives (Llama, Mistral, Gemma) for coding tasks. - Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. ::: ### Choosing Your Tool -For this workshop, we recommend: -- **New to AI coding?** Start with **Cursor** for the smoothest experience -- **Already using VS Code or can run local models?** Try **Cline** for seamless integration -- **Comfortable with CLI?** **Claude Code** gives you full control - -All three tools work well with the AI-enhanced extension template we'll use next. - -### Further Reading +For this workshop: +- We would start with with **Cursor** +- We would run through the same steps again using **Claude Code** +:::{note} Further Reading +:class: dropdown - [Simon Willison's blog on AI-assisted programming](https://simonwillison.net/tags/ai-assisted-programming/) โ€” Practical insights from a prolific developer - ["Not all AI-assisted programming is vibe coding (but vibe coding rocks)"](https://simonwillison.net/2025/Mar/19/vibe-coding/) โ€” Understanding different AI coding styles - [Claude Sonnet 4.5 review](https://simonwillison.net/2025/Sep/29/claude-sonnet-4-5/) โ€” Simon calls it "the best coding model in the world" @@ -246,350 +241,15 @@ All three tools work well with the AI-enhanced extension template we'll use next - [Simon Willison's Model Recommendations](https://simonwillison.net/2025/Oct/) โ€” Qwen3 and GLM-4.5 for open-source coding; Chinese models often outperform Western alternatives - [Ollama Documentation](https://ollama.com/) โ€” Self-hosting open-source models - [LM Studio](https://lmstudio.ai/) โ€” GUI for running local LLMs - - -## Getting Started: Clone the AI-Enhanced Extension Template - -For this module, we'll use an enhanced version of the JupyterLab extension template that includes AI-specific configurations and rulesets. - -:::{important} Prerequisites -* `copier`. Install with any of the following options: - * Into an existing environment (don't forget to activate it first): - * `pip install "copier~=9.2" jinja2-time` - * `conda install -c conda-forge "copier~=9.2" jinja2-time` - * Globally: - * `uv tool install --with jinja2-time copier~=9.2` - * `pixi global install "copier~=9.2" --with jinja2-time` -::: - -### Clone and setup the template - -1. Change to the parent directory where you want to work: - - ```bash - cd ~/Projects - ``` - -2. Create an extension repo in a new directory: - - ```bash - mkdir my-jupyterlab-extension - cd my-jupyterlab-extension - git init - ``` - -3. Instantiate the AI-enhanced template: - - ```bash - copier copy --trust https://github.com/orbrx/extension-template-cursor . - ``` - -:::{note} -By JupyterCon 2025, these AI-specific enhancements should be merged into the [official JupyterLab extension template](https://github.com/jupyterlab/extension-template). -::: - - -## ๐Ÿ”ง Exercise 0 (15 minutes): Configure Your AI Development Environment - -Before jumping into code generation, let's set up the "invisible infrastructure" that makes AI assistants work well. This configuration is what makes the difference between mediocre and excellent AI-generated code. - -### Pre-Exercise Checklist - -Make sure you have: - -- [ ] Git repository initialized with clean working tree -- [ ] Your AI tool installed (Cursor, Claude Code, or Cline) -- [ ] Extension template cloned or previous exercise code available -- [ ] Basic understanding: AI is a junior developer (95% accuracy = success!) - -### Part 1: Understand Your AI Rules (AGENTS.md) - -**What are AI Rules?** - -AI Rules (also called Cursor Rules, or system prompts) are instructions that automatically precede every conversation with your AI assistant. They're like permanent coaching that guides the AI's behavior. - -**AGENTS.md: The Emerging Standard** - -In 2025, the AI coding ecosystem converged on **AGENTS.md** as the universal format for agent instructions. Created through collaboration between OpenAI, Google, Sourcegraph, and others, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. - -**Tool Support Status:** - -| Tool | Support | Format | -|------|---------|--------| -| **Cursor** | โœ… Native | AGENTS.md + .cursor/rules/ | -| **GitHub Copilot** | โœ… Native | AGENTS.md (maintains .github/copilot-instructions.md for backward compatibility) | -| **Zed Editor** | โœ… Native | AGENTS.md | -| **Roo Code** | โœ… Native | AGENTS.md | -| **Claude Code** | โš™๏ธ Via symlink | Create: `ln -s AGENTS.md CLAUDE.md` | -| **Gemini CLI** | โš™๏ธ Config | Add to .gemini/settings.json: `{"context": {"fileName": ["AGENTS.md"]}}` | -| **Aider** | โš™๏ธ Config | Add to .aider.conf.yml: `read: AGENTS.md` | -| **Continue.dev** | โŒ Not yet | Use .continue/rules/ | -| **Cline** | โŒ Not yet | Use .clinerules/rules.md | - -**Key advantage:** Single AGENTS.md file works across your entire team regardless of which AI tool they useโ€”no more maintaining separate .cursorrules, CLAUDE.md, and GEMINI.md files. - - -For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. - -#### What's in Your AGENTS.md File - -1. **Check that the file exists:** - - ```bash - ls -la AGENTS.md - cat AGENTS.md # Review the contents - ``` - -2. **Key sections you'll find:** - - **Code Quality Rules:** - - Use structured logging (not `console.log()`) - - Define explicit TypeScript interfaces - - Avoid the `any` type - - Use typeguards over type casts - - **Naming Conventions:** - - Python: `snake_case` for functions, `PascalCase` for classes - - TypeScript: `camelCase` for variables, `PascalCase` for classes - - No mixing styles! - - **Project Structure Guidelines:** - - Frontend code in `src/` - - Backend Python in `/` - - Commands registered in `src/index.ts` - - Routes in `/routes.py` - - **JupyterLab-Specific Patterns:** - - How to register commands - - When to use `ReactWidget` vs `Widget` - - REST API best practices with `ServerConnection` - - State persistence with `IStateDB` - - **Development Workflow:** - - When to run `jlpm build` (TypeScript changes) - - When to restart Jupyter (Python changes) - - How to debug (browser console, terminal logs) - - **Common Pitfalls to Avoid:** - - โŒ Don't use `document.getElementById()` (use JupyterLab APIs) - - โŒ Don't hardcode URLs (use `ServerConnection.makeSettings()`) - - โŒ Don't forget `dispose()` methods (prevents memory leaks) - - โŒ Don't mix `npm` and `jlpm` (use `jlpm` only) - -**Why this matters:** These rules teach AI the JupyterLab patterns **before** it writes any code. Without them, AI might use generic React patterns or wrong APIs. With them, AI generates code that follows JupyterLab conventions from the start. - -3. **Verify AI recognizes the rules:** - - :::{note} No Visual Indicator - Cursor automatically reads and applies AGENTS.md, but there's **no visual indicator** in the interface showing it's active. - [![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) - ::: - - Open Cursor, start a new chat (Ask Mode), and ask: - ``` - What package manager should I use for JupyterLab extensions? - ``` - - AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! - - Try another test: - ``` - What TypeScript conventions should we follow? - ``` - - AI should mention strict mode, camelCase, avoiding `any` type, etc. - - **If AI gives wrong answers** (like suggesting `npm` instead of `jlpm`): - - Restart Cursor - - Make sure AGENTS.md is in your project root (not a subdirectory) - - Check that the file is named exactly `AGENTS.md` (case-sensitive) - -:::{tip} The Magic Behind Good AI Code -When you see AI generate well-structured JupyterLab code in exercises, it's not magic - it's reading your AGENTS.md file! These rules are why AI knows to: -- Use `jlpm` instead of `npm` -- Put commands in `src/index.ts` -- Extend the right base classes -- Follow JupyterLab naming patterns - -Without proper AI context that AGENTS.md provides, we observed AI generating generic code, or using the wrong package manager or thinking too much about source vs. prebuilt extensions. -::: - -If you'd like, you can modify the provided AI rules to include your favorite tools, package managers, and conventions. - -### Part 2: Register Official Documentation - -AI models may not have current JupyterLab documentation in their training data. Let's fix that: - -#### For Cursor Users: - -1. **Open Cursor Settings:** - - macOS: `Cmd + ,` then click "Cursor Settings" (not regular Settings) - - Windows/Linux: `Ctrl + ,` then click "Cursor Settings" - -2. **Navigate to Docs:** - - Settings โ†’ Cursor โ†’ Docs - - Click "Add Docs" - -3. **Add these documentation sources:** - - | Name | URL | - |------|-----| - | JupyterLab | `https://jupyterlab.readthedocs.io/` | - | Lumino | `https://lumino.readthedocs.io/` | - | Jupyter Server | `https://jupyter-server.readthedocs.io/` | - -4. **Add dependency documentation** (if using these libraries): - - | Name | URL | When to add | - |------|-----|-------------| - | Pillow | `https://pillow.readthedocs.io/` | Image processing | - | Pandas | `https://pandas.pydata.org/docs/` | Data manipulation | - | Plotly | `https://plotly.com/python/` | Interactive plots | - -5. **Test documentation integration:** - - In Cursor chat: - ``` - Using @JupyterLab documentation, how do I create a command that opens a new widget? - ``` - - AI should reference actual JupyterLab APIs and link to docs. - -#### For Claude Code / Cline Users: - -Documentation integration requires additional setup: - -- **Claude Code:** Documentation is NOT built into Claude's training. You need to: - - Enable web search in settings, OR - - Use MCP (Model Context Protocol) tools to fetch documentation, OR - - Manually paste relevant documentation into your prompts -- **Cline:** Can be configured to access web docs via settings - -### Part 3: Set Up Your Git Workflow - -**Critical:** Your Git game must be top-shelf when working with AI. - -1. **Enable source control panel** (Cursor/VS Code): - - Open the Source Control view (`Cmd/Ctrl + Shift + G`) - - Keep this panel visible alongside your AI chat - -2. **Understand the Four Safety Levels:** - - ``` - Level 1: Unsaved โ†’ Files on disk (Cmd/Ctrl + Z to undo) - Level 2: Staged โ†’ git add (can unstage) - Level 3: Committed โ†’ git commit (can reset) - Level 4: Pushed โ†’ git push (permanent) - ``` - -3. **Adopt this workflow:** - - ```bash - # After AI generates code: - # 1. Review changes in Source Control panel - - # 2. Stage changes you like: - git add src/widget.ts # stage individual files - - # 3. If AI continues and breaks something: - git restore src/widget.ts # revert to staged version - - # 4. Once everything works: - git commit -m "Add image filter buttons with AI assistance" - - # 5. If you want to undo a commit: - git reset --soft HEAD~1 # keep changes, undo commit - ``` - -4. **Configure Git to show better diffs:** - - ```bash - # Better word-level diffs for TypeScript/Python - git config --global diff.algorithm histogram - - # Show function names in diff headers - git config --global diff.python.xfuncname '^[ \t]*((class|def)[ \t].*)$' - ``` - -:::{danger} Git is Your Safety Net -AI can suggest code that breaks your extension. With frequent commits and staging, you can fearlessly experiment and roll back instantly. **This is not optional**โ€”it's how you work safely with AI. ::: -### Part 4: Choose Your AI Model Wisely - -**Model selection impacts both quality and cost.** - -#### For Cursor Users: - -1. **Enable model selector:** - - Settings โ†’ Cursor Settings โ†’ General - - Find "Usage Summary" โ†’ Set to "Always" (not "Auto") - - This shows your credit usage at bottom of chat panel +## Getting Started -2. **Choose models strategically:** +### Repo +For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from <...>. - | Task | Recommended Model | Why | - |------|------------------|-----| - | Planning & Reasoning | GPT-5 or Claude Sonnet 4.5 (Thinking) | GPT-5 leads reasoning benchmarks; Claude excellent for extended thinking | - | Coding (Best Overall) | Claude Sonnet 4.5 | "Best coding model in the world" per Simon Willison; 99.29% safety rate | - | Long Coding Sessions | Claude 4 Opus | Sustains focus for 30+ hours, ideal for large refactors and multi-step tasks | - | Speed & Long Context | Gemini 2.5 Pro | 1M token context, sub-second streaming, best latency | - | Quick fixes | Claude Haiku 4.5 or GPT-5 Mini | Faster, cheaper for simple edits and routine tasks | - | Local Development | GLM-4.5 Air, Qwen3-235B, or DeepSeek-R1 | Best open models for self-hosting on consumer hardware | - | Avoid | Auto | Cursor picks cheapest, not best | - -3. **Watch your context usage:** - - Look for percentage in chat (e.g., "23.4%") - - Keep under 50% for best results - - Above 70%? Start a new chat - -4. **Monitor credits:** - - Check `cursor.com/settings` โ†’ Usage - - Typical costs: Planning ($1-2), Implementation ($0.30-0.50) - -:::{tip} Start Big, Optimize Later -For planning and architecture, always use the highest-quality model available: -- **Cloud:** GPT-5 for reasoning, Claude Sonnet 4.5 for coding -- **Self-hosted:** DeepSeek-R1 or Qwen3-235B-A22B - -You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM-4.5 Air) for routine edits, but don't skimp on the thinking phase. -::: - -### Verification: Is Your Environment Ready? - -Run this quick test to verify everything is configured: - -1. **Open your extension in Cursor/AI tool** - -2. **Start a new chat and ask:** - - ``` - Please verify my JupyterLab development setup: - 1. Check that TypeScript is configured correctly - 2. Verify Python package structure - 3. List the coding standards I should follow - 4. Tell me what documentation you have access to - ``` - -3. **AI should respond with:** - - โœ… References to your project structure - - โœ… Mentions rules from AGENTS.md (strict mode, camelCase, etc.) - - โœ… Shows awareness of JupyterLab patterns - - โœ… (If using Cursor) Mentions registered documentation - -4. **If something's missing:** - - Restart Cursor/AI tool - - Check that AGENTS.md or .cursor/rules exist - - Verify documentation was added to Cursor settings - -:::{admonition} You're Ready! -:class: tip -Once AI can reference your project structure, coding rules, and JupyterLab documentation, you're set up for success. This configuration is what separates "AI that generates random code" from "AI that writes code that matches your project's patterns." -::: - ---- - -## ๐Ÿ‹๏ธ Exercise 1 (45 minutes): Extending the Image Viewer with AI +In our initial setup, we cloned an official JupyterLab extension template. +This template was recently enhanced to include AI-specific configurations and rulesets. In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. @@ -616,7 +276,7 @@ If you completed the anatomy module and want to continue with your extension: jupyter lab ``` -4. Skip to [Verify AI Configuration](#verify-ai-configuration) below. +4. Skip to [AI tool](#ai-tool) below. ### Option 2: Clone the Finished Extension @@ -653,957 +313,37 @@ If you'd prefer to start fresh or didn't complete the anatomy module: jupyter lab ``` -### Verify AI Configuration - -Your extension should include AI-specific configuration files that help guide AI assistants in understanding JupyterLab extension development patterns. - -1. Check for the AI ruleset file: - - ```bash - # Look for either of these files in your extension root - ls -la AGENTS.md CLAUDE.md 2>/dev/null - ``` - - :::{important} - If neither `AGENTS.md` nor `CLAUDE.md` exists, **let an instructor know!** These files contain important context and rules that help AI assistants generate better JupyterLab extension code. - ::: - -2. Review the ruleset file (optional but recommended): - - ```bash - # View the AI ruleset - cat AGENTS.md # or CLAUDE.md - ``` - - These files should be identical and include: - - JupyterLab architecture patterns and best practices - - Common pitfalls to avoid - - TypeScript/React conventions for JupyterLab - - Testing and debugging guidance - - Links to official documentation and APIs - -### Understanding Your Starting Point - -Before we extend the functionality, let's review what the extension currently does: - -**Frontend (TypeScript):** -- `src/index.ts`: Plugin registration, command definitions, launcher/palette integration -- `src/widget.ts`: Main UI widget that displays images and captions -- `src/request.ts`: Utility for communicating with the backend API - -**Backend (Python):** -- `jupytercon2025_extension_workshop/routes.py`: REST API handlers -- `jupytercon2025_extension_workshop/images_and_captions.py`: Image metadata -- `jupytercon2025_extension_workshop/images/`: Image files on disk - -**Current Features:** -- โœ… Displays random images from a curated collection -- โœ… Shows captions for each image -- โœ… Refresh button to load a new random image -- โœ… Layout restoration (widget persists across JupyterLab sessions) - -### Goal: Add Image Editing Capabilities - -We'll use AI to extend this viewer with basic image editing features. This exercise demonstrates: -- **Planning first, coding second** - Let AI architect before implementing -- How to communicate complex feature requirements to an AI assistant -- How AI can help navigate unfamiliar APIs (PIL/Pillow for image processing) -- The iterative development cycle: plan โ†’ implement โ†’ test โ†’ refine -- How to debug and fix issues with AI assistance -- Managing AI context with fresh chats for new phases - -### Step 1: The Power (and Peril) of One-Shot Prompts - -Before we dive into our structured approach, let's witness what modern AI can accomplish with a single, well-crafted prompt. This demonstration shows both the impressive capabilities and important limitations of AI-driven development. - -:::{admonition} One-Shot Prompt Magic -:class: note - -With the right context and a detailed prompt, AI can build complete features in minutes. Here's a prompt that could generate our entire image editing extension: - -``` -Extend this image viewer extension to add image editing capabilities: - -Add editing controls to the widget: -- Buttons for filters: grayscale, sepia, blur, sharpen -- Basic crop functionality (50% crop from center) -- Brightness/contrast adjustments (slider controls) -- Save edited image back to disk - -Use Pillow (PIL) on the backend to process images. The backend should: -- Accept the image filename and editing operation via REST API -- Apply the transformation using appropriate Pillow methods -- Return the processed image to the frontend as base64-encoded data - -The frontend should: -- Update the displayed image immediately after each edit -- Show the current filter/transformation applied -- Allow chaining multiple edits before saving - -Technical requirements: -- Add Pillow to the Python dependencies -- Create a new REST endpoint `/edit-image` in routes.py -- Add filter buttons to the widget toolbar -- Maintain the existing refresh functionality -``` -::: - -#### What Happens with This Prompt? - -When you give this prompt to an AI agent like Cursor or Claude Code, it will typically: - -1. **Analyze your existing codebase** to understand the current structure -2. **Make architectural decisions** about implementation patterns -3. **Generate 200+ lines of code** across multiple files -4. **Update dependencies** in pyproject.toml -5. **Create new endpoints** in your backend -6. **Modify the frontend widget** with new UI controls -7. **Run build commands** to verify everything compiles - -**In about 2-3 minutes**, you will have a fully functional image editor! - -#### The Hidden Cost: Decisions Made Without You - -While impressive, this one-shot approach makes numerous decisions on your behalf: - -**Architecture Decisions:** -- โ“ Should images be processed server-side or client-side? -- โ“ Base64 encoding vs. temporary file URLs? -- โ“ Stateful vs. stateless image processing? -- โ“ Where to store edited images? - -**UI/UX Decisions:** -- โ“ Button placement and styling -- โ“ Slider ranges and defaults -- โ“ Error message presentation -- โ“ Loading state indicators - -**Technical Implementation:** -- โ“ PIL filter parameters (blur radius, sharpen intensity) -- โ“ Image format handling (JPEG quality, PNG transparency) -- โ“ Memory management for large images -- โ“ Caching strategy for processed images - -**Code Quality:** -- โ“ Error handling approach -- โ“ TypeScript type definitions -- โ“ Python type hints -- โ“ Test coverage - -:::{warning} The Product Manager Trap -When you use one-shot prompts, you're essentially saying: "AI, you be the product manager, architect, and developer all at once." - -This works great for prototypes, but in production code, you need to understand and own these decisions. -::: - -#### Try It Yourself (Optional Demo) - -If you want to experience the one-shot approach: - -1. **Give your AI the one-shot prompt above** - -2. **Watch as it generates the entire feature** (2-3 minutes) - -3. **Test the functionality:** - ```bash - jlpm build - jupyter lab - ``` - -4. **Examine what was created:** - - Look at the code structure - - Notice the architectural choices - - Find at least 3 decisions you might have made differently - -5. **Roll back when done:** - -#### The Better Way: Structured, Iterative Development - -While one-shot prompts are impressive for demos, professional development requires a more thoughtful approach. We'll now proceed with a structured workflow that: - -1. **Plans before coding** - Understand the architecture first -2. **Implements in phases** - Build incrementally with checkpoints -3. **Reviews each step** - Catch issues early -4. **Maintains control** - You make the key decisions - -This takes longer but results in: -- โœ… Code you understand and can maintain -- โœ… Architecture that fits your needs -- โœ… Proper error handling and edge cases -- โœ… Learning opportunities at each step - -### Step 2: Create an Implementation Plan - -**The rise of the Product Manager mindset:** AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. - -Before generating any code, we'll have AI create a phased implementation plan. This: -- โœ… Keeps AI focused and prevents scope creep -- โœ… Gives you a roadmap to refer back to -- โœ… Makes it easy to resume work across sessions -- โœ… Documents architectural decisions - -1. **Create a plans directory:** - - ```bash - mkdir plans - ``` - -2. **Start a new chat in Cursor** and use this prompt: - - ```` - I'm extending a JupyterLab image viewer to add image editing capabilities. - - Please create a detailed implementation plan and save it to plans/image-editing-feature.md - - **Requirements:** - - Add filter buttons (grayscale, sepia, blur, sharpen) - - Use Pillow (PIL) on the backend for processing - - New REST endpoint `/edit-image` for transformations - - Update frontend to display edited images immediately - - Basic crop functionality (50% from center) - - Brightness/contrast sliders - - Save edited image back to disk - - **DO NOT WRITE CODE YET.** Create a phased plan with: - - **Phase 1: MVP** - - Basic filter buttons (grayscale, sepia) - - Backend endpoint scaffolding - - Frontend display of processed images - - **Phase 2: Advanced Filters** - - Blur and sharpen filters - - Crop functionality - - Brightness/contrast adjustments - - **Phase 3: Polish** - - Save functionality - - Undo/redo buttons - - Loading states and error handling - - For each phase, list: - - Specific files to create/modify - - Python/TypeScript dependencies needed - - Testing approach - - Potential issues to watch for - - Save this plan to plans/image-editing-feature.md - ```` - -3. **Review the plan:** - - Open `plans/image-editing-feature.md` - - Read through each phase - - Ask questions if anything is unclear: - ``` - In Phase 1, why did you choose to handle images as base64? - What are the alternatives? - ``` - -4. **Commit the plan:** - - ```bash - git add plans/image-editing-feature.md - git commit -m "Add implementation plan for image editing feature" - ``` - -**Why this matters:** You now have a versioned plan that AI (and you) can reference. As you work through phases, AI will stay focused on the current step. - -:::{tip} Planning Mode vs. File-Based Plans -Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-based plans are: -- โœ… Versioned in Git -- โœ… Synced across machines -- โœ… Reviewable and editable -- โœ… Persistent across sessions - -Always save plans to files: `plans/*.md` -::: - -### Step 3: Implement Phase by Phase - -**Context window management:** Instead of one long chat for everything, start fresh for each phase. - -1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + K`) - -2. **Reference the plan:** - - ``` - We are ready for Phase 1 of @plans/image-editing-feature.md - - Please implement the MVP: basic grayscale and sepia filters with - backend endpoint and frontend display. - ``` - - Note the `@plans/...` syntax tells AI to read that specific file. - -3. **Review changes in Source Control** (keep this panel open!) - -4. **Commit after Phase 1 works:** - - ```bash - git add . - git commit -m "Phase 1: Add basic image filters (grayscale, sepia)" - ``` - -5. **Start ANOTHER fresh chat for Phase 2:** - - ``` - We are ready for Phase 2 of @plans/image-editing-feature.md - - Phase 1 is complete. Now implement advanced filters (blur, sharpen, crop). - ``` - -**Why new chats?** Keeps context window small (<50%), prevents AI confusion, reduces costs. File-based plans allow us to start new chats and still keep the required context. - -### Step 4: Adopt a "Product Manager" Mindset - -**Effective prompts use Product Manager thinking: clear requirements, constraints, and acceptance criteria.** - -Instead of: -> "Add some image filters to the viewer" - -Try this structure: - -````markdown -**User Story:** As a user, I want to apply filters to images before analyzing them. - -**Acceptance Criteria:** -- [ ] Clicking "Grayscale" converts image to grayscale immediately -- [ ] Changes are visible without page reload -- [ ] Original image is preserved (can reset) -- [ ] Multiple filters can be applied in sequence -- [ ] Error messages show if filter fails - -**Technical Requirements:** -- Backend: Use Pillow's `ImageFilter` module -- API: POST to `/api//edit-image` - - Request: `{filename: string, filter_type: string, params?: object}` - - Response: `{success: boolean, image_data: string (base64)}` -- Frontend: Add toolbar buttons using `@jupyterlab/apputils` `Toolbar` -- State: Track edit history in widget state - -**Non-Requirements (for later):** -- Don't implement save functionality yet (Phase 3) -- Don't need undo/redo (Phase 3) -- Don't worry about performance optimization - -**Questions for AI:** -- What's the best way to handle large images? -- Should we cache the base64 data or re-fetch? -- How do we prevent race conditions if user clicks multiple filters quickly? -```` - -This level of detail helps AI give you exactly what you want. - -### The Prompts - -We'll demonstrate this development process using two different AI tools: -- **Cursor AI** (IDE-integrated approach) -- **Claude Code** (CLI-based approach) - -Here's the implementation prompt for Phase 1 (feel free to adapt it): - -````markdown -Extend this image viewer extension to add image editing capabilities: - -Add editing controls to the widget: -- Buttons for filters: grayscale, sepia, blur, sharpen -- Basic crop functionality (50% crop from center) -- Brightness/contrast adjustments (slider controls) -- Save edited image back to disk - -Use Pillow (PIL) on the backend to process images. The backend should: -- Accept the image filename and editing operation via REST API -- Apply the transformation using appropriate Pillow methods -- Return the processed image to the frontend as base64-encoded data - -The frontend should: -- Update the displayed image immediately after each edit -- Show the current filter/transformation applied -- Allow chaining multiple edits before saving - -Technical requirements: -- Add Pillow to the Python dependencies -- Create a new REST endpoint `/edit-image` in routes.py -- Add filter buttons to the widget toolbar -- Maintain the existing refresh functionality -```` - -:::{tip} Prompt Engineering: The Complete Guide -**Effective prompts use the "Product Manager" structure:** - -**1. User Story** (sets context): -``` -As a data scientist, I want to apply image filters before analysis, -so I can preprocess images without leaving JupyterLab. -``` - -**2. Specific Requirements** (what to build): -``` -- Add filter buttons: grayscale, sepia, blur, sharpen -- Filters apply immediately (no reload) -- Multiple filters can be chained -- Original image can be restored -``` - -**3. Technical Constraints** (how to build): -``` -- Backend: Use Pillow (PIL) ImageFilter module -- API: POST /api//edit-image -- Frontend: Toolbar buttons using @jupyterlab/apputils -- State: Track applied filters for undo -``` - -**4. Acceptance Criteria** (how to verify): -``` -- [ ] Clicking "Grayscale" converts image -- [ ] Change is visible within 500ms -- [ ] Console shows no errors -- [ ] Multiple filters can apply sequentially -``` - -**5. Non-Requirements** (scope control): -``` -- Don't implement save yet (Phase 2) -- Don't worry about performance optimization -- Don't need keyboard shortcuts -``` - -**6. Questions for AI** (get expert input): -``` -- What's the best way to handle large images? -- Should we cache processed images? -- How do we prevent race conditions? -``` - -**Ask AI to cite documentation:** -- "Reference @JupyterLab docs for widget lifecycle" -- "Link to Pillow API for each filter you use" -- "Show TypeScript type definitions from @jupyterlab/services" - -**Request explanations (learn while building):** -- "Explain why you chose this approach over [alternative]" -- "Comment the code explaining the processing pipeline" -- "What are the tradeoffs of backend vs. frontend processing?" - -**Provide feedback loops:** -- "This works, but the sepia is too strongโ€”make it 50% less intense" -- "Good approach, but let's refactor to extract the filter logic" -- "Perfect! Now add tests for edge cases" - -**The more specific you are, the better AI performs.** -::: +Make sure your git tree is clean, there are no unsaved and uncommited files. This is going to be important later -### Before You Start: Choose Your AI Tool +### AI tool -Pick **one** of the following paths: +We will be using Cursor and Claude Code throughout this tutorial. Please, install them if you would like to follow. -- ๐Ÿ‘‰ [Path A: Cursor AI (IDE)](#path-a-cursor-ai-ide) - Recommended for beginners -- ๐Ÿ‘‰ [Path B: Claude Code (CLI)](#path-b-claude-code-cli) - For terminal enthusiasts +You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. ---- - -## Path A: Cursor AI (IDE) - -We'll demonstrate the complete development workflow using Cursor AI, showing you how to iterate on the prompt and debug issues. - -:::{note} Live Demo -An instructor will demonstrate this path in real-time. Follow along or take notes to try it yourself afterward. -::: - -### Setup Cursor AI - -1. Download and install Cursor from [cursor.com](https://cursor.com/) - -2. Configure your API key or subscription: - - **Option 1:** Sign up for Cursor subscription ($20/month, includes API access) - - **Option 2:** Bring your own OpenAI or Anthropic API key - - Open Settings (`Cmd/Ctrl + ,`) - - Navigate to "Cursor" โ†’ "API Keys" - - Add your key - -3. Open your extension folder in Cursor: - - ```bash - cursor ~/Projects/jupytercon2025-extension-workshop - ``` - -### Using Cursor to Generate Code - -1. **Open the Cursor Chat panel** (`Cmd/Ctrl + L`) - -2. **Add context files** by clicking the `+` button or dragging files: - - `src/widget.ts` (where UI changes will go) - - `jupytercon2025_extension_workshop/routes.py` (where backend changes will go) - - `AGENTS.md` or `CLAUDE.md` (AI ruleset with JupyterLab patterns) - - `package.json` and `pyproject.toml` (for dependency context) - -3. **Paste the prompt** from above into the chat - -4. **Review the generated code**: - - Cursor will suggest changes across multiple files - - Read through each change carefully - - Look for comments explaining the approach - - Check if dependencies were added correctly - -5. **Accept or modify** the suggestions: - - Click "Accept" to apply all changes - - Or click individual files to review and edit before accepting - -### Testing and Debugging - -1. **Install new Python dependencies** (if Pillow was added): - - ```bash - pip install --editable ".[dev,test]" - ``` - -2. **Rebuild the extension**: - - ```bash - jlpm build - ``` - -3. **Restart JupyterLab** (stop with `Ctrl+C`, then): - - ```bash - jupyter lab - ``` - -4. **Test the new features**: - - Open the image viewer widget - - Try each filter button - - Check the browser console for errors (`F12` or `Cmd+Option+I`) - - Check the terminal running `jupyter lab` for Python errors - -### Common Issues and How to Fix Them with AI - -**The debugging workflow:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. - -#### Issue 1: Build Errors (TypeScript) - -**Symptom:** `jlpm build` fails with TypeScript errors - -**Instead of manually debugging:** - -1. **Copy the full error output** - -2. **Ask AI to diagnose and fix:** - ``` - The build failed with these TypeScript errors: - - [paste error output] - - Please: - 1. Explain what's causing this - 2. Fix the issues - 3. Explain why the fix works - ``` - -**AI will:** -- Identify the root cause (missing import, wrong type, etc.) -- Fix the code across multiple files if needed -- Run `jlpm build` again to verify -- Explain the issue so you learn - -**Example error AI handles well:** -``` -error TS2339: Property 'filterType' does not exist on type 'IImageEditRequest' -``` - -AI will add the property to the interface definition automatically. - -#### Issue 2: Runtime Errors (Browser Console) - -**Symptom:** Extension loads but features don't work; errors in browser console - -**Debugging with AI:** - -1. **Open browser console** (`F12` or `Cmd+Option+I`) - -2. **Copy the error and screenshot the UI state** - -3. **Provide both to AI:** - ``` - The filter buttons don't work. Here's what happens: - - [drag screenshot showing the UI] - - Console error: - [paste error] - - What's wrong? Fix the issue and explain. - ``` - -**AI excels at:** -- Simple async/await syntax errors (missing await, incorrect .then() usage) -- API request failures (CORS, wrong endpoint, malformed JSON) -- Event handler issues (button not triggering callback) -- State management bugs (React re-render issues) - -**Pro tip:** Use visual context! Screenshot shows AI exactly what's broken. - -#### Issue 3: Backend Errors (Python Traceback) - -**Symptom:** Python server crashes or endpoint returns 500 error - -**AI debugging workflow:** - -1. **Find the full traceback in terminal** (where `jupyter lab` is running) - -2. **Copy everything** (don't excerptโ€”AI needs full context) - -3. **Paste into chat:** - ``` - The /edit-image endpoint is failing. Full traceback: - - [paste entire traceback] - - The request body is: - {"filename": "image.jpg", "filter_type": "grayscale"} - - Fix the Pillow image processing logic and explain the issue. - ``` - -**AI will:** -- Identify which line causes the crash -- Check if it's a logic error, missing dependency, or type issue -- Fix the code -- Suggest tests to prevent regression - -**Common issues AI catches:** -- File path errors (image not found) -- Pillow API misuse (wrong filter parameters) -- Missing error handling (unhandled exceptions) -- JSON serialization issues (can't encode PIL.Image objects) - -#### Issue 4: Extension Not Loading - -**Symptom:** Extension doesn't appear in JupyterLab after build - -**Systematic AI debugging:** - -``` -My extension isn't loading in JupyterLab. Please check: - -1. Is the extension properly installed? Run: jupyter labextension list -2. Is the server extension enabled? Run: jupyter server extension list -3. Check the browser console for any plugin loading errors -4. Verify the plugin ID in package.json matches what's exported in src/index.ts - -Report findings and fix any issues. -``` - -**AI will systematically check** each step and fix configuration issues. - -#### Issue 5: AI Generates Wrong Code - -**Symptom:** AI's solution doesn't match what you asked for - -**How to redirect AI:** - -โŒ **Don't say:** "This is wrong, try again" - -โœ… **Do say:** -``` -This almost works, but I wanted X instead of Y. - -Current behavior: When I click "Grayscale", nothing happens -Expected behavior: Image should convert to grayscale immediately - -The issue seems to be in the event handler. Can you check if: -- The button's onClick is wired correctly? -- The API request is actually being sent? -- The response is updating the widget state? - -Please fix and explain what was wrong. -``` - -**Provide:** -- Specific behavior delta (current vs. expected) -- Your hypothesis about where the issue is -- Request explanation (AI teaches you debugging) - -#### Issue 6: AI Is Confused or Gives Inconsistent Answers - -**Symptom:** AI suggests code that contradicts what it just wrote, or seems "lost" - -**Likely cause:** Context window is too full (>70%) - -**Solution:** -1. **Start a fresh chat** (`Cmd/Ctrl + K`) -2. **Reference your plan:** - ``` - Continuing work on @plans/image-editing-feature.md - - Phase 1 is complete (committed). I'm debugging an issue where [describe issue]. - - The error is: [paste error] - ``` - -3. **Add relevant files to context:** - - Click `+` in chat - - Add only the files related to the bug (widget.ts, routes.py) - - Don't add the entire project - -**Fresh context = fresh perspective.** - -:::{danger} When AI Debugging Isn't Working -If after 3-4 iterations AI isn't solving the issue: - -1. **Take a break** - Fresh eyes help (you and AI) -2. **Read the code yourself** - You might spot the issue -3. **Search documentation** - AI might be hallucinating an API -4. **Ask a human** - Instructors, teammates, Jupyter Zulip chat -5. **Simplify the problem** - Create minimal reproduction - -AI is powerful but not omniscient. Sometimes manual debugging is faster. -::: - -### AI-Powered Debugging Checklist - -Before asking AI for help, gather: - -- [ ] Full error message (not just first line) -- [ ] Code context (what file, what function) -- [ ] What you were trying to do -- [ ] What happened instead -- [ ] Steps to reproduce -- [ ] Screenshots (for UI issues) - -**More context = better AI debugging.** - -### Iterating on the Prompt - -After getting basic functionality working, try refining with follow-up prompts: - -``` -The sepia filter looks too strong. Make it more subtle. -``` - -``` -Add undo/redo buttons to revert edits. -``` - -``` -Save the edit history so I can see what filters were applied. -``` - -:::{important} ๐Ÿ’พ **Make Git commits as you go!** -After each successful change: - -```bash -git add . -git commit -m "Add [specific feature] with AI assistance" -``` - -This makes it easy to revert if AI suggests something that breaks your extension. -::: - -### Advanced: Visual Design with Screenshots - -AI can understand screenshots! This is incredibly powerful for debugging UI issues or communicating design intent. - -#### Debugging UI with Screenshots - -1. **Take a screenshot** of the issue (Cmd+Shift+4 on Mac, Snipping Tool on Windows) - -2. **Drag screenshot into Cursor chat:** - - ``` - The filter button spacing looks off [drag screenshot here] - - Please adjust the CSS so buttons have: - - 8px margin between them - - 16px padding inside each button - - Match JupyterLab's standard button styling from @jupyterlab/ui-components - ``` - -3. **AI will:** - - Analyze the visual layout - - Identify specific CSS issues - - Propose fixes targeting the exact elements - -#### Communicating Design Intent - -You can show AI examples of what you want: - -``` -I want the filter toolbar to look like this [drop screenshot of Photoshop's filter panel] - -Match this visual style: -- Icon-based buttons (not text) -- Tooltips on hover -- Grouped by filter category -- Same color scheme as JupyterLab's toolbar -``` - -AI will interpret the visual design and generate appropriate code. - -#### When to Use Screenshots: - -- โœ… Layout/spacing problems ("buttons are misaligned") -- โœ… Color/theming issues ("doesn't match JupyterLab theme") -- โœ… Showing desired design ("make it look like this") -- โœ… Demonstrating bugs ("the image disappears when I click this") -- โœ… Component placement ("move this above the image") - -#### Example Workflow: - -``` -1. Implement feature -2. Take screenshot of result -3. Drag to AI: "The buttons should be above the image, not below" -4. AI adjusts layout -5. Reload, take new screenshot -6. Drag to AI: "Perfect, but reduce the spacing by half" -``` - -**This iterative visual feedback loop is remarkably effective.** - -### Managing Context and Costs - -As you work through phases, keep an eye on these metrics: - -**Context window percentage** (shown in Cursor chat): -- **< 30%:** Healthy, plenty of room -- **30-50%:** Good, AI still focused -- **50-70%:** Getting crowded, consider new chat soon -- **> 70%:** Start new chat immediately - -**When to start a new chat:** -- โœ… Completed a phase (Phase 1 โ†’ Phase 2) -- โœ… Switching focus areas (backend โ†’ frontend) -- โœ… Context above 50% -- โœ… AI seems confused or gives inconsistent answers -- โœ… Major refactoring complete - -**How to maintain continuity:** -``` -New chat: "We are ready for Phase 2 of @plans/image-editing-feature.md - -Phase 1 is complete and committed. Now implementing advanced filters." -``` - -The plan file provides context without bloating the chat window. - ---- - -## Path B: Claude Code (CLI) - -For developers who prefer working in the terminal, Claude Code provides a powerful command-line interface for AI-assisted development. - -:::{note} Live Demo -An instructor will demonstrate this path in real-time. Follow along or take notes to try it yourself afterward. -::: - -### Setup Claude Code - -1. Install Node.js (if not already installed): - - ```bash - # macOS (using Homebrew) - brew install node@18 - - # Ubuntu/Debian - curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - - sudo apt install -y nodejs - - # Verify installation - node -v && npm -v - ``` - -2. Install Claude Code: - - ```bash - npm install -g @anthropic/claude-code - ``` - -3. Configure your Anthropic API key: - - ```bash - export ANTHROPIC_API_KEY="your-api-key-here" - ``` - - :::{tip} - Add this to your `~/.bashrc` or `~/.zshrc` to persist across sessions: - ```bash - echo 'export ANTHROPIC_API_KEY="your-key"' >> ~/.zshrc - ``` - ::: - -4. Navigate to your extension directory: - - ```bash - cd ~/Projects/jupytercon2025-extension-workshop - ``` - -### Using Claude Code to Generate Code - -1. **Start an interactive session**: - - ```bash - claude-code - ``` - -2. **Provide context** by referencing files in your prompt: - - ``` - I'm working on a JupyterLab extension. Please read these files for context: - - src/widget.ts - - jupytercon2025_extension_workshop/routes.py - - AGENTS.md - - package.json - - pyproject.toml - - [Then paste the main prompt about adding image editing capabilities] - ``` - -3. **Review and apply changes**: - - Claude Code will show diffs for each file - - Type `y` to accept, `n` to skip, or `e` to edit - - Changes are applied directly to your files - -### Testing and Debugging - -Follow the same testing workflow as Path A: - -1. Install dependencies: `pip install --editable ".[dev,test]"` -2. Rebuild: `jlpm build` -3. Restart: `jupyter lab` -4. Test in the browser - -For errors, paste them back into Claude Code: - -``` -I got this error when testing the grayscale filter: - -[paste error] - -Please fix it. -``` - -### Claude Code Tips - -**Run commands without leaving the chat:** - -``` -Can you also run `jlpm build` to verify this compiles? -``` - -**Ask for explanations:** - -``` -Before you change the code, explain how Pillow's ImageFilter.BLUR works -and why you're choosing this approach. -``` - -**Request tests:** - -``` -Generate pytest tests for the new /edit-image endpoint. -``` - ---- +## Getting Started (15 minutes) +- set up repo from previous exercise or checkpoint +- install cursor and claude code (existing installed tools are fine to use too) + +## Exercise A (15 minutes): Understand AI rules +- Inspect AGENTS.md +- Familiarize yourself with UI of Cursor +- Choose AI model +- Ask AI chat questions to verify that it recognizes the rules + +## Exercise B (30 minutes): Build it! +- Discuss our goal briefly (go from image viewer to image editor) +- Send a single prompt to Cursor +- Basic debugging +- Power and peril of one-shot prompts + +### Exercise C (20 minutes): Product Manager framework +- Learn how to use structured approach to AI-assisted development +- User stories + +### Demo: AI from the Command Line (10 minutes) +- Demonstrate using Claude Code for development workflow +- Study hall is a good time to try it out ## Reflection and Next Steps @@ -1634,71 +374,8 @@ We'll share experiences as a group and create a live poll about which techniques ### Key Takeaways -โœ… **AI excels at:** -- **Scaffolding and boilerplate** - New endpoints, UI components, tests -- **Navigating unfamiliar APIs** - Pillow, new JupyterLab features -- **Systematic tasks** - Following patterns, applying transforms -- **Explanation and education** - "Why did you choose this approach?" -- **Self-correction** - Fixing build errors, addressing type issues -- **Iterative refinement** - Adjusting based on your feedback - -โš ๏ธ **AI may struggle with:** -- **Complex architectural decisions** - When to use State DB vs. props -- **Complex async bugs** - Race conditions, timing issues, subtle promise chaining errors -- **Performance optimization** - Knowing when code is "fast enough" -- **Project-specific conventions** - Without AGENTS.md guidance -- **Ambiguous requirements** - "Make it better" vs. specific criteria - -๐ŸŽฏ **The sweet spot:** -AI is most effective when you provide: -1. **Clear requirements** (Product Manager mindset) -2. **Project context** (AGENTS.md rules, documentation) -3. **Phased plans** (not trying to do everything at once) -4. **Iterative feedback** (junior developer coaching) -5. **Safety nets** (Git commits, testing) - -:::{tip} Best Practices from This Exercise -1. **Plan before coding**: Written plans keep AI focused across sessions -2. **Start fresh chats**: Keep context <50% for best results -3. **Commit frequently**: After every working phase -4. **Review everything**: You wouldn't merge a PR without review -5. **Ask questions**: "Why did you choose X over Y?" -6. **Provide feedback**: "This works, but let's refactor for clarity" -7. **Use visual tools**: Screenshots communicate design intent effectively -8. **Monitor context**: Watch that percentage, start new chats proactively -9. **Document patterns**: Update AGENTS.md when you establish new conventions -10. **Learn from AI**: Request explanations to understand generated code -::: - ### Challenge Extensions (Optional) -If you finish early or want to continue exploring, try these follow-up prompts: - -1. **Add image rotation:** - ``` - Add 90-degree rotation buttons (clockwise and counter-clockwise) - ``` - -2. **Implement a filter preview:** - ``` - Show a small preview thumbnail for each filter before applying it - ``` - -3. **Add keyboard shortcuts:** - ``` - Add keyboard shortcuts for common filters (g for grayscale, s for sepia) - ``` - -4. **Export functionality:** - ``` - Add a "Download" button that saves the edited image to the user's computer - ``` - -5. **Preset combinations:** - ``` - Add preset buttons that apply multiple filters at once - (e.g., "Vintage" = sepia + slight blur) - ``` :::{important} ๐Ÿ’พ **Final Git commit and push!** ```bash diff --git a/04-materials/07-independent-work.md b/04-materials/07-independent-work.md index d28da3bf..20bc7ae6 100644 --- a/04-materials/07-independent-work.md +++ b/04-materials/07-independent-work.md @@ -43,6 +43,8 @@ Create something entirely new using the extension template, AI assistance, and t 3. Choose your extension type based on your idea: - **Frontend only**: For UI-only features (widgets, buttons, panels) - **Frontend + Server**: When you need Python backend logic (data processing, file system access, external APIs) + - **Theme**: Customize the look and feel of JupyterLab by creating your own theme extension (colors, fonts, icons) + 4. Install and verify the base extension works: @@ -242,11 +244,9 @@ Pick an idea that matches your comfort level and interests: 2. **Git status widget**: Display current branch, uncommitted changes count 3. **Environment inspector**: Show installed packages and Python/Node versions 4. **Todo list with persistence**: Sidebar panel that saves tasks to disk -5. **Markdown preview**: Add support for custom syntax like mermaid diagrams **Advanced**: -1. **Collaborative cursor**: Show where teammates are working (requires WebSocket) -2. **AI code review**: Send selected code to an LLM and show suggestions +2. **AI code assistant**: Create a chat with LLM 3. **Performance profiler**: Instrument notebook cells and show execution metrics 4. **Custom file format viewer**: Add support for viewing proprietary file types 5. **Real-time log viewer**: Stream and filter server logs in a widget @@ -270,30 +270,7 @@ Contributing to established extensions is a great way to learn real-world patter Here are actively-maintained JupyterLab extensions that welcome contributions: -**Data Visualization & Analysis**: -- [jupyterlab-matplotlib](https://github.com/matplotlib/ipympl) - Interactive matplotlib widgets -- [jupyterlab-plotly](https://github.com/plotly/plotly.py) - Plotly chart integration -- [jupyterlab-bokeh](https://github.com/bokeh/jupyterlab_bokeh) - Bokeh visualization support - -**Development Tools**: -- [jupyterlab-git](https://github.com/jupyterlab/jupyterlab-git) - Git integration UI -- [jupyterlab-lsp](https://github.com/jupyter-lsp/jupyterlab-lsp) - Language Server Protocol support -- [jupyterlab-code-formatter](https://github.com/ryantam626/jupyterlab_code_formatter) - Code formatting tools - -**Collaboration & Sharing**: -- [jupyter-collaboration](https://github.com/jupyterlab/jupyter-collaboration) - Real-time collaboration -- [jupyterlab-commenting](https://github.com/jupyterlab/jupyterlab-commenting) - Add comments to notebooks -- [jupyterlab-drawio](https://github.com/QuantStack/jupyterlab-drawio) - Diagram editor integration - -**File Viewers & Editors**: -- [jupyterlab-myst](https://github.com/executablebooks/jupyterlab-myst) - MyST markdown support -- [jupyterlab-geojson](https://github.com/jupyterlab/jupyter-renderers) - GeoJSON file viewer -- [jupyterlab-fasta](https://github.com/jupyterlab/jupyter-renderers) - Biological sequence viewer - -**Productivity**: -- [jupyterlab-execute-time](https://github.com/deshaw/jupyterlab-execute-time) - Show cell execution times -- [jupyterlab-spellchecker](https://github.com/jupyterlab-contrib/spellchecker) - Spell checking for markdown -- [jupyterlab-variableInspector](https://github.com/lckr/jupyterlab-variableInspector) - Variable explorer +TODO: Add them ### How to Find Good First Issues @@ -566,7 +543,7 @@ Before the session ends, take 10 minutes to: - Subscribe to issues on extensions you've contributed to - Join the [Jupyter Zulip chat](https://jupyter.zulipchat.com) for real-time discussions - Participate in the [Jupyter Discourse forum](https://discourse.jupyter.org/) -- Attend [Jupyter Community Calls](https://discourse.jupyter.org/t/jupyter-community-calendar/2485) - bring your questions or request code review from experienced developers +- Attend [Jupyter Community Calls](https://jupyter.org/community#calendar) - bring your questions or request code review from experienced developers - Help others getting started :::{tip} The Journey Continues From 5d63c5e6851c22f3d957e05e36272aa5b4bf16bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:45:16 +0000 Subject: [PATCH 017/118] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 04-materials/06-developing-with-ai.md | 52 +++++++++++------------ 04-materials/07-independent-work.md | 59 +++++++++++++-------------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 6a4f8602..0795b54c 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -53,7 +53,7 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural ### Where LLMs Live: Deployment Models **Frontier Models (Cloud-Hosted):** -- **Examples:** +- **Examples:** - **Claude Sonnet 4.5** (Anthropic): Best coding model, $3/$15 per M tokens - **GPT-5** (OpenAI): Best overall reasoning, $1.25/$10 per M tokens - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal @@ -169,7 +169,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca #### 1. **Cursor** (Popular UI-First IDE) - **What it is:** A fork of VS Code with deep AI integration -- **LLM Options:** +- **LLM Options:** - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) - Bring your own API key (OpenAI, Anthropic, Google, or other providers) - Can be configured to use local models (Qwen3-Coder, GLM-4.5, DeepSeek-R1) via OpenAI-compatible APIs @@ -248,7 +248,7 @@ For this workshop: ### Repo For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from <...>. -In our initial setup, we cloned an official JupyterLab extension template. +In our initial setup, we cloned an official JupyterLab extension template. This template was recently enhanced to include AI-specific configurations and rulesets. In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. @@ -270,7 +270,7 @@ If you completed the anatomy module and want to continue with your extension: ```bash # Activate your environment micromamba activate jupytercon2025 - + # Build and start JupyterLab jlpm build jupyter lab @@ -302,12 +302,12 @@ If you'd prefer to start fresh or didn't complete the anatomy module: # Create/activate environment micromamba create -n jupytercon2025-ai python pip nodejs gh "copier~=9.2" jinja2-time micromamba activate jupytercon2025-ai - + # Install the extension in development mode pip install --editable ".[dev,test]" jupyter labextension develop . --overwrite jupyter server extension enable jupytercon2025_extension_workshop - + # Build and start JupyterLab jlpm build jupyter lab @@ -322,25 +322,25 @@ We will be using Cursor and Claude Code throughout this tutorial. Please, instal You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. ## Getting Started (15 minutes) -- set up repo from previous exercise or checkpoint -- install cursor and claude code (existing installed tools are fine to use too) - -## Exercise A (15 minutes): Understand AI rules -- Inspect AGENTS.md -- Familiarize yourself with UI of Cursor -- Choose AI model -- Ask AI chat questions to verify that it recognizes the rules - -## Exercise B (30 minutes): Build it! -- Discuss our goal briefly (go from image viewer to image editor) -- Send a single prompt to Cursor -- Basic debugging -- Power and peril of one-shot prompts - -### Exercise C (20 minutes): Product Manager framework -- Learn how to use structured approach to AI-assisted development -- User stories - +- set up repo from previous exercise or checkpoint +- install cursor and claude code (existing installed tools are fine to use too) + +## Exercise A (15 minutes): Understand AI rules +- Inspect AGENTS.md +- Familiarize yourself with UI of Cursor +- Choose AI model +- Ask AI chat questions to verify that it recognizes the rules + +## Exercise B (30 minutes): Build it! +- Discuss our goal briefly (go from image viewer to image editor) +- Send a single prompt to Cursor +- Basic debugging +- Power and peril of one-shot prompts + +### Exercise C (20 minutes): Product Manager framework +- Learn how to use structured approach to AI-assisted development +- User stories + ### Demo: AI from the Command Line (10 minutes) - Demonstrate using Claude Code for development workflow - Study hall is a good time to try it out @@ -353,7 +353,7 @@ After completing this exercise (via either path), take a moment to reflect: Think about these questionsโ€”we'll discuss as a group: -1. **What surprised you most about working with AI?** +1. **What surprised you most about working with AI?** - Did it understand JupyterLab patterns better or worse than expected? - Were there moments where it "just got it" vs. moments where you had to guide it heavily? diff --git a/04-materials/07-independent-work.md b/04-materials/07-independent-work.md index 20bc7ae6..e5bb0d42 100644 --- a/04-materials/07-independent-work.md +++ b/04-materials/07-independent-work.md @@ -3,8 +3,8 @@ :::{hint} Session Format This is **independent exploration time** - similar to office hours or a study hall. Work at your own pace on a project that interests you. Instructors are available to help when you get stuck. -**Duration**: 120 minutes (until the end of the day) -**Format**: Self-directed with instructor support +**Duration**: 120 minutes (until the end of the day) +**Format**: Self-directed with instructor support **Goal**: Apply what you've learned by building something new or contributing to an existing extension ::: @@ -44,7 +44,7 @@ Create something entirely new using the extension template, AI assistance, and t - **Frontend only**: For UI-only features (widgets, buttons, panels) - **Frontend + Server**: When you need Python backend logic (data processing, file system access, external APIs) - **Theme**: Customize the look and feel of JupyterLab by creating your own theme extension (colors, fonts, icons) - + 4. Install and verify the base extension works: @@ -52,10 +52,10 @@ Create something entirely new using the extension template, AI assistance, and t # Install in development mode pip install --editable ".[dev,test]" jupyter labextension develop . --overwrite - + # If you chose frontend + server: jupyter server extension enable - + # Build and test jlpm build jupyter lab @@ -69,13 +69,13 @@ These three examples have been tested and confirmed working. Pick one that match #### ๐ŸŽ‰ Confetti Celebration Button -**Complexity**: Beginner (Frontend only) -**Time**: 15-20 minutes +**Complexity**: Beginner (Frontend only) +**Time**: 15-20 minutes **What you'll learn**: Status bar integration, DOM manipulation, visual effects **The Prompt**: ``` -Add a button in the status bar to celebrate something! It should show confetti +Add a button in the status bar to celebrate something! It should show confetti on top of the UI for 3 seconds ``` @@ -90,7 +90,7 @@ on top of the UI for 3 seconds If the confetti doesn't appear: ``` -The button is there but I don't see confetti. Check that the z-index +The button is there but I don't see confetti. Check that the z-index is high enough and the confetti container is properly positioned. ``` @@ -112,8 +112,8 @@ This is a perfect first project - it's self-contained, purely visual, and you'll #### ๐ŸŽจ Custom Theme Extension -**Complexity**: Beginner (Frontend only) -**Time**: 20-30 minutes +**Complexity**: Beginner (Frontend only) +**Time**: 20-30 minutes **What you'll learn**: Theme customization, CSS styling, JupyterLab theming system **The Prompt**: @@ -136,19 +136,19 @@ Create a theme based on Netflix show KPop Demon Hunters To add a background image to the main panel: ``` -Can we use this image as a background for main panel? +Can we use this image as a background for main panel? [paste your image URL] ``` Example: ``` -Can we use this image as a background for main panel? +Can we use this image as a background for main panel? https://www.billboard.com/wp-content/uploads/2025/07/kpop-demon-hunters-billboard-1800.jpg?w=942&h=628&crop=1 ``` To refine the styling: ``` -The background image is too bright - make it more subtle with reduced opacity +The background image is too bright - make it more subtle with reduced opacity and add a dark overlay so text is readable. ``` @@ -165,7 +165,7 @@ Use [specific colors] for accent elements. - Free image sources: Unsplash, Pexels, Wallhaven :::{tip} -This is a perfect creative project! You get immediate visual feedback, can personalize your JupyterLab environment, and learn how JupyterLab's theming system works. Plus, you'll have a custom theme you actually want to use daily. +This is a perfect creative project! You get immediate visual feedback, can personalize your JupyterLab environment, and learn how JupyterLab's theming system works. Plus, you'll have a custom theme you actually want to use daily. Themes are also great conversation starters - share your theme with other workshop participants! ::: @@ -174,15 +174,15 @@ Themes are also great conversation starters - share your theme with other worksh #### ๐Ÿ“Š CPU Monitor Widget -**Complexity**: Intermediate (Frontend + Server) -**Time**: 30-40 minutes +**Complexity**: Intermediate (Frontend + Server) +**Time**: 30-40 minutes **What you'll learn**: REST API integration, backend data processing, graceful error handling **The Prompt**: ``` Create a JupyterLab extension that monitors CPU stats: - Use psutil on the backend to get utilization and temperature data -- Create a REST API endpoint +- Create a REST API endpoint - Display it in a widget in the main area - Handle gracefully if some fields aren't available ``` @@ -199,19 +199,19 @@ Create a JupyterLab extension that monitors CPU stats: If `psutil` isn't installed: ``` -I'm getting ModuleNotFoundError for psutil. Update pyproject.toml to +I'm getting ModuleNotFoundError for psutil. Update pyproject.toml to include psutil as a dependency. ``` If the widget doesn't update: ``` -The widget shows data once but doesn't update. Add automatic polling +The widget shows data once but doesn't update. Add automatic polling every 2 seconds to refresh the CPU stats. ``` To extend it: ``` -Add a graph that shows CPU usage over the last 60 seconds, +Add a graph that shows CPU usage over the last 60 seconds, and highlight in red when usage is above 80%. ``` @@ -298,7 +298,7 @@ TODO: Add them # Fork the repository on GitHub first, then: git clone https://github.com/YOUR-USERNAME/extension-name.git cd extension-name - + # Follow the project's CONTRIBUTING.md instructions # Usually similar to: pip install --editable ".[dev,test]" @@ -324,7 +324,7 @@ TODO: Add them # Run the project's test suite jlpm test pytest - + # Test manually in JupyterLab jupyter lab ``` @@ -348,12 +348,12 @@ TODO: Add them AI can help you understand existing code: ``` -Explain how this widget's lifecycle works - especially the initialize() +Explain how this widget's lifecycle works - especially the initialize() and dispose() methods. ``` ``` -I want to add a new filter option to this panel. Show me where to add +I want to add a new filter option to this panel. Show me where to add the UI component and how to wire it to the existing filtering logic. ``` @@ -409,17 +409,17 @@ These are bite-sized experiments you can complete quickly: Use these prompts with your AI assistant to explore JupyterLab's capabilities: ``` -Show me all the different places I can add UI elements in JupyterLab +Show me all the different places I can add UI elements in JupyterLab (toolbar, menu bar, status bar, sidebar, etc.) with minimal code examples. ``` ``` -Create a simple example of each JupyterLab widget type: MainAreaWidget, +Create a simple example of each JupyterLab widget type: MainAreaWidget, Panel, ToolbarButton, and Dialog. ``` ``` -Demonstrate how to listen for JupyterLab events: file opened, cell executed, +Demonstrate how to listen for JupyterLab events: file opened, cell executed, theme changed, etc. ``` @@ -438,7 +438,7 @@ Pick an extension from the list in Path 2 and explore its codebase: **Use AI to understand**: ``` -I'm reading the jupyterlab-git extension. Explain how the git panel +I'm reading the jupyterlab-git extension. Explain how the git panel widget is registered and what lifecycle methods it implements. ``` @@ -553,4 +553,3 @@ The patterns you've learned here - using AI assistance effectively, reading docu Keep building, keep learning, and welcome to the JupyterLab extension developer community! ๐Ÿš€ ::: - From d7bbf067bf24700278a4ca17354e0b42aa5b54b0 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 27 Oct 2025 12:47:43 -0600 Subject: [PATCH 018/118] Typo --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 0795b54c..62bcd695 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -313,7 +313,7 @@ If you'd prefer to start fresh or didn't complete the anatomy module: jupyter lab ``` -Make sure your git tree is clean, there are no unsaved and uncommited files. This is going to be important later +Make sure your git tree is clean, there are no unsaved and uncommitted files. This is going to be important later ### AI tool From 64724eb6efe828169fa6170e0c756e7d94fb69e5 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:05:38 -0700 Subject: [PATCH 019/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 62bcd695..5b55e238 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -351,7 +351,7 @@ After completing this exercise (via either path), take a moment to reflect: ### Quick Reflection (Optional) -Think about these questionsโ€”we'll discuss as a group: +Think about these questions โ€” we'll discuss as a group: 1. **What surprised you most about working with AI?** - Did it understand JupyterLab patterns better or worse than expected? From d063e098371d28a2ce51e541429f8691ea61730e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:06:22 -0700 Subject: [PATCH 020/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 5b55e238..a2711140 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -18,7 +18,7 @@ After this module, you will have: If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. -### Setting Expectations: Your AI Partner is Like a Skilled Junior Developer +### Setting Expectations: Your AI Partner is Like a Knowledgeable Junior Developer Before we dive into tools and techniques, let's set the right mindset for working with AI. From 362078df0043339bea2e5f1a4b585c1db741235d Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:06:56 -0700 Subject: [PATCH 021/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index a2711140..17b33c96 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -32,7 +32,7 @@ Before we dive into tools and techniques, let's set the right mindset for workin - โœ… Gets better with guidance and context **The right mindset:** -- Don't get frustrated by small mistakesโ€”iterate and guide +- Don't get frustrated by small mistakes โ€” iterate and guide - Review all generated code (you wouldn't merge a PR without review) - Ask questions: "Why did you choose this approach?" - Treat errors as learning opportunities for both you and the AI From 7f30441da9d676479e0ab6dd8c34af2a8d18d97b Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:09:41 -0700 Subject: [PATCH 022/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 17b33c96..a1ebcabd 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -14,7 +14,7 @@ After this module, you will have: :::: -## AI-Assisted Development in 2025 +## AI-assisted development in 2025 If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. From 8acaa6cda0288a8b0cb5f2065d8b3f14cca78322 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:15:49 -0700 Subject: [PATCH 023/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index a1ebcabd..af5602c2 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -22,7 +22,7 @@ If you haven't used AI-assisted development tools yet, you're about to experienc Before we dive into tools and techniques, let's set the right mindset for working with AI. -**Don't expect perfection.** If AI gets 95% of what you need correct, that's incredibleโ€”exactly what you'd get from a talented junior developer given the same instructions. +**Don't expect perfection.** If AI gets 95% of what you need correct, that's incredible โ€” exactly what you'd get from a talented junior developer given the same instructions. **Key mental model:** Think of your AI assistant as a fast, eager junior developer who: - โœ… Learns quickly and has read tons of documentation (but maybe not the latest stuff) From aeb0b534f5dc4fbe9abe4f5b24d9c7524e92f249 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:22:21 -0700 Subject: [PATCH 024/118] sentence case for h2 and lower headers --- 04-materials/06-developing-with-ai.md | 48 +++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index af5602c2..340def4c 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -18,7 +18,7 @@ After this module, you will have: If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. -### Setting Expectations: Your AI Partner is Like a Knowledgeable Junior Developer +### Setting expectations: your AI partner is like a knowledgeable junior developer Before we dive into tools and techniques, let's set the right mindset for working with AI. @@ -42,7 +42,7 @@ Before we dive into tools and techniques, let's set the right mindset for workin You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. ::: -### Understanding LLMs (Large Language Models) +### Understanding LLMs (large language models) AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural networks trained on vast amounts of text and code. These models can: - Understand context from your codebase @@ -50,7 +50,7 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural - Explain existing code and suggest improvements - Debug errors by analyzing stack traces and code patterns -### Where LLMs Live: Deployment Models +### Where LLMs live: deployment models **Frontier Models (Cloud-Hosted):** - **Examples:** @@ -63,13 +63,13 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural - **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed - **Cons:** Requires internet connection, ongoing costs, data leaves your machine -**Mid-Tier and Efficient Models (Cloud or Local):** +**Mid-tier and efficient models (cloud or local):** - **Examples:** Claude Haiku, Qwen3-30B-A3B (approaching GPT-4o performance), Mistral Small 3.2, Llama 3.3-70B - **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware - **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency - **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ VRAM) -**Open-Source & Open-Weight Models (2025 State-of-the-Art):** +**Open-source & open-weight models (2025 state-of-the-art):** - **Highly Recommended for Coding:** - **Qwen3-235B-A22B** (Apache 2.0): 235B params with 22B active, 262K context, exceptional reasoning - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware @@ -83,11 +83,11 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural - **Pros:** Full control, no API costs, data stays local, latest Chinese models often outperform Western alternatives - **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) -### Understanding AI Coding Tool Categories +### Understanding AI coding tool categories Not all AI coding tools are created equal. Understanding the different categories helps you choose the right tool and set appropriate expectations. -#### โŒ Chat-Based AI (ChatGPT, Claude web interface) +#### โŒ Chat-based AI (ChatGPT, Claude web interface) **How it works:** - You paste code snippets โ†’ AI gives you code back @@ -163,11 +163,11 @@ Agentic AI: If you've tried AI coding before and found it frustrating, chances are you were using chat-based AI or basic autocomplete. The techniques in this workshop are designed for **agentic, tool-using AI** that can understand and operate on your full project. ::: -### AI IDEs and Tools for Extension Development +### AI IDEs and tools for extension development Now that you understand the categories, here are the **agentic AI tools** you can use in this workshop, ranging from GUI-first to CLI-native: -#### 1. **Cursor** (Popular UI-First IDE) +#### 1. **Cursor** (popular UI-first IDE) - **What it is:** A fork of VS Code with deep AI integration - **LLM Options:** - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) @@ -176,7 +176,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - **Best for:** Developers who want a polished, GUI-driven experience - **Download:** [cursor.com](https://cursor.com/) -#### 2. **Cline** (Open-Source VS Code Extension) +#### 2. **Cline** (open-source VS Code extension) - **What it is:** A VS Code extension (formerly Claude Dev) that provides AI-assisted coding - **LLM Options:** - Bring your own API key (OpenAI, Anthropic, Google, etc.) @@ -185,7 +185,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - **Best for:** Developers who prefer VS Code and want flexibility - **Install:** Search "Cline" in VS Code extensions or visit [github.com/cline/cline](https://github.com/cline/cline) -#### 3. **Claude Code** (CLI for Power Users) +#### 3. **Claude Code** (CLI for power users) - **What it is:** Command-line interface for Claude, optimized for coding workflows - **LLM Options:** - Requires Anthropic API key @@ -226,7 +226,7 @@ ollama run deepseek-r1 Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. ::: -### Choosing Your Tool +### Choosing your tool For this workshop: - We would start with with **Cursor** @@ -243,7 +243,7 @@ For this workshop: - [LM Studio](https://lmstudio.ai/) โ€” GUI for running local LLMs ::: -## Getting Started +## Getting started ### Repo For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from <...>. @@ -253,7 +253,7 @@ This template was recently enhanced to include AI-specific configurations and ru In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. -### Option 1: Continue with Your Own Extension +### Option 1: Continue with your own extension If you completed the anatomy module and want to continue with your extension: @@ -278,7 +278,7 @@ If you completed the anatomy module and want to continue with your extension: 4. Skip to [AI tool](#ai-tool) below. -### Option 2: Clone the Finished Extension +### Option 2: Clone the finished extension If you'd prefer to start fresh or didn't complete the anatomy module: @@ -321,7 +321,7 @@ We will be using Cursor and Claude Code throughout this tutorial. Please, instal You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. -## Getting Started (15 minutes) +## Getting started (15 minutes) - set up repo from previous exercise or checkpoint - install cursor and claude code (existing installed tools are fine to use too) @@ -337,19 +337,19 @@ You are totally welcome to use any AI tool you have installed on your computer! - Basic debugging - Power and peril of one-shot prompts -### Exercise C (20 minutes): Product Manager framework +### Exercise C (20 minutes): Product manager framework - Learn how to use structured approach to AI-assisted development - User stories -### Demo: AI from the Command Line (10 minutes) +### Demo: AI from the command line (10 minutes) - Demonstrate using Claude Code for development workflow - Study hall is a good time to try it out -## Reflection and Next Steps +## Reflection and next steps After completing this exercise (via either path), take a moment to reflect: -### Quick Reflection (Optional) +### Quick reflection (optional) Think about these questions โ€” we'll discuss as a group: @@ -372,9 +372,9 @@ Think about these questions โ€” we'll discuss as a group: We'll share experiences as a group and create a live poll about which techniques resonated most. Your instructor will facilitate this discussion. ::: -### Key Takeaways +### Key takeaways -### Challenge Extensions (Optional) +### Challenge extensions (optional) :::{important} ๐Ÿ’พ **Final Git commit and push!** @@ -387,7 +387,7 @@ git push --- -## What's Next? +## What's next? You've now experienced the complete AI-assisted development workflow: - โœ… Used AI to generate code for new features @@ -395,7 +395,7 @@ You've now experienced the complete AI-assisted development workflow: - โœ… Learned to provide effective context and constraints - โœ… Understood when to accept AI suggestions vs. when to customize -### Continuing Your Journey +### Continuing your journey The next chapter, {doc}`07-independent-work`, provides **independent exploration time** where you can: From b9ec23538f2430289901d1fb106d5ebd00509559 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:22:36 -0700 Subject: [PATCH 025/118] remove for now --- 04-materials/07-independent-work.md | 555 ---------------------------- 1 file changed, 555 deletions(-) delete mode 100644 04-materials/07-independent-work.md diff --git a/04-materials/07-independent-work.md b/04-materials/07-independent-work.md deleted file mode 100644 index e5bb0d42..00000000 --- a/04-materials/07-independent-work.md +++ /dev/null @@ -1,555 +0,0 @@ -# ๐Ÿš€ 7 - Independent Work: Build or Contribute - -:::{hint} Session Format -This is **independent exploration time** - similar to office hours or a study hall. Work at your own pace on a project that interests you. Instructors are available to help when you get stuck. - -**Duration**: 120 minutes (until the end of the day) -**Format**: Self-directed with instructor support -**Goal**: Apply what you've learned by building something new or contributing to an existing extension -::: - -:::{tip} Choose Your Own Adventure -You have three paths: - -1. **Build from Scratch** - Create a new extension using the template and AI assistance -2. **Contribute to Existing Extensions** - Add features or fix issues in established projects -3. **Explore and Experiment** - Try multiple small ideas to deepen your understanding - -All paths are valuable! Pick what excites you most. -::: - - -## Path 1: Build Your Own Extension from Scratch - -Create something entirely new using the extension template, AI assistance, and the patterns you've learned. - -### Quick Start - -1. Create a new extension from the template: - - ```bash - cd ~/Projects - mkdir my-jupyterlab-extension - cd my-jupyterlab-extension - git init - ``` - -2. Instantiate the template: - - ```bash - copier copy --trust https://github.com/jupyterlab/extension-template . - ``` - -3. Choose your extension type based on your idea: - - **Frontend only**: For UI-only features (widgets, buttons, panels) - - **Frontend + Server**: When you need Python backend logic (data processing, file system access, external APIs) - - **Theme**: Customize the look and feel of JupyterLab by creating your own theme extension (colors, fonts, icons) - - -4. Install and verify the base extension works: - - ```bash - # Install in development mode - pip install --editable ".[dev,test]" - jupyter labextension develop . --overwrite - - # If you chose frontend + server: - jupyter server extension enable - - # Build and test - jlpm build - jupyter lab - ``` - -### Tested Example Projects - -These three examples have been tested and confirmed working. Pick one that matches your interests - whether you want visual effects, creative customization, or full-stack development. Use them as-is to practice prompt engineering, or as inspiration for your own ideas. - ---- - -#### ๐ŸŽ‰ Confetti Celebration Button - -**Complexity**: Beginner (Frontend only) -**Time**: 15-20 minutes -**What you'll learn**: Status bar integration, DOM manipulation, visual effects - -**The Prompt**: -``` -Add a button in the status bar to celebrate something! It should show confetti -on top of the UI for 3 seconds -``` - -**Expected Results**: -- Status bar shows a celebration button (usually bottom-right) -- Clicking triggers confetti animation overlay -- Animation appears on top of all UI elements -- Confetti clears after ~3 seconds -- Multiple clicks work correctly - -**What to Watch For**: - -If the confetti doesn't appear: -``` -The button is there but I don't see confetti. Check that the z-index -is high enough and the confetti container is properly positioned. -``` - -To customize: -``` -Make the confetti more colorful and add a sound effect when it triggers. -``` - -**Verified Setup**: -- โœ… macOS 15.7, Claude Code (Claude Sonnet 4.5) -- โœ… `conda` environment: Python 3.13, Node.js 22, JupyterLab -- ๐Ÿ“น [Watch the demo](https://www.loom.com/share/2afabea0184045fa868271f9ab0ca083) - -:::{tip} -This is a perfect first project - it's self-contained, purely visual, and you'll immediately see if it works. It teaches you about JupyterLab's status bar API without backend complexity. -::: - ---- - -#### ๐ŸŽจ Custom Theme Extension - -**Complexity**: Beginner (Frontend only) -**Time**: 20-30 minutes -**What you'll learn**: Theme customization, CSS styling, JupyterLab theming system - -**The Prompt**: -``` -Create a theme based on [your favorite movie/show/game/aesthetic] -``` - -**Example**: -``` -Create a theme based on Netflix show KPop Demon Hunters -``` - -**Expected Results**: -- A new theme appears in Settings โ†’ Theme menu -- Theme includes custom colors matching your chosen aesthetic -- JupyterLab interface reflects the theme's visual style -- Theme can be toggled on/off - -**What to Watch For**: - -To add a background image to the main panel: -``` -Can we use this image as a background for main panel? -[paste your image URL] -``` - -Example: -``` -Can we use this image as a background for main panel? -https://www.billboard.com/wp-content/uploads/2025/07/kpop-demon-hunters-billboard-1800.jpg?w=942&h=628&crop=1 -``` - -To refine the styling: -``` -The background image is too bright - make it more subtle with reduced opacity -and add a dark overlay so text is readable. -``` - -To adjust colors: -``` -Update the sidebar and toolbar colors to match the theme's color palette. -Use [specific colors] for accent elements. -``` - -**Tips for Finding Images**: -- Search "[theme name] wallpaper 4k" for high-quality backgrounds -- Look for images with good contrast for text readability -- Consider color palettes - extract 3-5 main colors from your chosen image (AI can help you with it!) -- Free image sources: Unsplash, Pexels, Wallhaven - -:::{tip} -This is a perfect creative project! You get immediate visual feedback, can personalize your JupyterLab environment, and learn how JupyterLab's theming system works. Plus, you'll have a custom theme you actually want to use daily. - -Themes are also great conversation starters - share your theme with other workshop participants! -::: - ---- - -#### ๐Ÿ“Š CPU Monitor Widget - -**Complexity**: Intermediate (Frontend + Server) -**Time**: 30-40 minutes -**What you'll learn**: REST API integration, backend data processing, graceful error handling - -**The Prompt**: -``` -Create a JupyterLab extension that monitors CPU stats: -- Use psutil on the backend to get utilization and temperature data -- Create a REST API endpoint -- Display it in a widget in the main area -- Handle gracefully if some fields aren't available -``` - -**Expected Results**: -- Extension installs `psutil` successfully -- Widget appears in main area when launched -- CPU utilization percentage displays -- Updates automatically (polling every few seconds) -- No errors when temperature data is unavailable -- Widget layout is readable and organized - -**What to Watch For**: - -If `psutil` isn't installed: -``` -I'm getting ModuleNotFoundError for psutil. Update pyproject.toml to -include psutil as a dependency. -``` - -If the widget doesn't update: -``` -The widget shows data once but doesn't update. Add automatic polling -every 2 seconds to refresh the CPU stats. -``` - -To extend it: -``` -Add a graph that shows CPU usage over the last 60 seconds, -and highlight in red when usage is above 80%. -``` - -**Verified Setup**: -- โœ… macOS 15.7, Cursor (Claude Sonnet 4.5 MAX) -- โœ… `conda` environment: Python 3.13, Node.js 22, JupyterLab -- โš ๏ธ Temperature data gracefully handled as N/A on macOS (expected) -- ๐Ÿ“น [Watch the demo](https://www.loom.com/share/9f6d11d537a94a30af7559fd4d80eea2) - -:::{tip} -This teaches you the full stack: backend API design, frontend-backend communication, error handling, and periodic updates. It's a perfect template for any monitoring or dashboard extension. -::: - ---- - -### More Ideas by Difficulty - -Pick an idea that matches your comfort level and interests: - -**Beginner (Frontend Only)**: -1. **Theme switcher dropdown**: Add a quick theme selector to the toolbar for easy switching -2. **Clock widget**: Status bar item showing current time with configurable timezone -3. **Quote of the day**: Main area widget that fetches and displays random quotes -4. **Keyboard shortcut viewer**: Panel showing all available shortcuts -5. **Custom welcome screen**: Override the default launcher with your own design -6. **Pomodoro timer**: Status bar timer for focused work sessions with notifications - -**Intermediate (Frontend + Server)**: -1. **File size analyzer**: Scan workspace directory and show largest files/folders -2. **Git status widget**: Display current branch, uncommitted changes count -3. **Environment inspector**: Show installed packages and Python/Node versions -4. **Todo list with persistence**: Sidebar panel that saves tasks to disk - -**Advanced**: -2. **AI code assistant**: Create a chat with LLM -3. **Performance profiler**: Instrument notebook cells and show execution metrics -4. **Custom file format viewer**: Add support for viewing proprietary file types -5. **Real-time log viewer**: Stream and filter server logs in a widget - - -## Path 2: Contribute to Existing Extensions - -Contributing to established extensions is a great way to learn real-world patterns and give back to the community. - -### Why Contribute? - -- Learn from production-quality code -- Work with maintainers who know JupyterLab well -- Your contribution helps thousands of users -- Build your portfolio and resume -- Connect with the Jupyter community - -### Finding Contribution Opportunities - -#### Extensions Looking for Contributors - -Here are actively-maintained JupyterLab extensions that welcome contributions: - -TODO: Add them - -### How to Find Good First Issues - -1. **Visit the repository** and look for labels: - - `good first issue` - - `help wanted` - - `beginner-friendly` - - `documentation` - -2. **Check the project board** or GitHub Issues for: - - Feature requests that align with your interests - - Bug reports you can reproduce - - Documentation improvements - -3. **Browse recent issues** and look for: - - Questions you can answer - - Problems you've encountered yourself - - Features you wish existed - -### Making Your First Contribution - -1. **Set up the development environment**: - - ```bash - # Fork the repository on GitHub first, then: - git clone https://github.com/YOUR-USERNAME/extension-name.git - cd extension-name - - # Follow the project's CONTRIBUTING.md instructions - # Usually similar to: - pip install --editable ".[dev,test]" - jupyter labextension develop . --overwrite - jlpm build - ``` - -2. **Create a branch** for your work: - - ```bash - git checkout -b fix/issue-123-button-alignment - ``` - -3. **Make your changes**: - - Start small - fix one thing at a time - - Follow the project's code style - - Add or update tests if applicable - - Update documentation - -4. **Test thoroughly**: - - ```bash - # Run the project's test suite - jlpm test - pytest - - # Test manually in JupyterLab - jupyter lab - ``` - -5. **Commit and push**: - - ```bash - git add . - git commit -m "Fix button alignment in toolbar (#123)" - git push -u origin fix/issue-123-button-alignment - ``` - -6. **Open a Pull Request**: - - Go to the original repository on GitHub - - Click "New Pull Request" - - Select your fork and branch - - Describe what you changed and why - - Reference the issue number if applicable - -:::{tip} Using AI for Contributions -AI can help you understand existing code: - -``` -Explain how this widget's lifecycle works - especially the initialize() -and dispose() methods. -``` - -``` -I want to add a new filter option to this panel. Show me where to add -the UI component and how to wire it to the existing filtering logic. -``` - -``` -This extension uses React. Convert this example to use React best practices -while maintaining compatibility with the existing component structure. -``` -::: - -### Contribution Checklist - -Before opening a PR, verify: - -- [ ] Code follows the project's style guide -- [ ] Changes are tested (automated tests and manual testing) -- [ ] Documentation is updated if needed -- [ ] Commit messages are clear and descriptive -- [ ] PR description explains what and why -- [ ] You've read and followed CONTRIBUTING.md -- [ ] Tests pass in CI/CD (after opening PR) - -### When Contributing Gets Stuck - -**If you're blocked**: -- Comment on the issue asking for clarification -- Join the [Jupyter Zulip chat](https://jupyter.zulipchat.com) (or project-specific chat if available) -- Ask instructors during this session -- It's okay to pause and try something else - -**If maintainers request changes**: -- Don't take it personally - it's about code quality -- Ask questions if feedback is unclear -- Make the requested changes iteratively -- Maintainers are volunteers - be patient and grateful - - -## Path 3: Explore and Experiment - -Not ready to commit to a full project? Try multiple small experiments to deepen your understanding. - -### Micro-Projects (10-15 minutes each) - -These are bite-sized experiments you can complete quickly: - -1. **Change an icon**: Pick an existing JupyterLab command and change its icon -2. **Add a menu item**: Create a new menu entry that opens a URL -3. **Create a notification**: Show a toast notification when JupyterLab loads -4. **Customize the theme**: Add custom CSS to change colors or fonts -5. **Log user actions**: Create a plugin that logs when users run cells - -### Exploration Prompts - -Use these prompts with your AI assistant to explore JupyterLab's capabilities: - -``` -Show me all the different places I can add UI elements in JupyterLab -(toolbar, menu bar, status bar, sidebar, etc.) with minimal code examples. -``` - -``` -Create a simple example of each JupyterLab widget type: MainAreaWidget, -Panel, ToolbarButton, and Dialog. -``` - -``` -Demonstrate how to listen for JupyterLab events: file opened, cell executed, -theme changed, etc. -``` - -``` -Show me how to store and retrieve user settings/preferences for my extension. -``` - -### Reading Code for Learning - -Pick an extension from the list in Path 2 and explore its codebase: - -1. **Start with `package.json`**: What dependencies does it use? -2. **Read `src/index.ts`**: How is the plugin structured? -3. **Find the widgets**: What UI components does it create? -4. **Trace a feature**: Pick one feature and follow it from UI to backend - -**Use AI to understand**: -``` -I'm reading the jupyterlab-git extension. Explain how the git panel -widget is registered and what lifecycle methods it implements. -``` - - -## General Guidance - -### Development Workflow (Reminder) - -The cycle you'll repeat many times: - -1. **Write a clear prompt** - Context, requirements, constraints -2. **Review generated code** - Read it, understand it, don't blindly accept -3. **Build**: `jlpm build` -4. **Test**: Refresh JupyterLab (or restart if backend changed) -5. **Debug**: Browser console and terminal for errors -6. **Iterate**: Refine your prompt based on results -7. **Commit**: Save working states frequently - -### When to Ask for Help - -Don't stay stuck! Ask an instructor if: - -- You've tried the same thing 3-4 times with no progress -- AI suggests something that contradicts JupyterLab patterns -- Build succeeds but feature doesn't appear -- You want guidance on architecture decisions -- You're unsure if your contribution approach is right - -:::{tip} Core Developers Are Here! -Many Jupyter and JupyterLab core developers are in the room and at JupyterCon 2025. This is a unique opportunity to: -- Ask questions about extension development directly from the experts -- Get feedback on your architecture and design decisions -- Learn about upcoming changes and best practices -- Discuss contribution opportunities -- Connect with the people who built the platform you're extending - -Don't be shy - they're here to help and love seeing new contributors! -::: - -**Asking well gets better help**: -- Share your exact error message -- Show what you've already tried -- Explain what you expected vs. what happened -- Include relevant code snippets - -### Making the Most of This Time - -**Focus on learning, not perfection**: -- Getting stuck and debugging teaches you more than smooth sailing -- Small working features beat ambitious broken ones -- Exploring multiple approaches builds intuition - -**Document your journey**: -- Take notes on what prompts work well -- Save error messages and solutions -- Write down "aha!" moments -- Update your extension's README as you go - -**Connect with others**: -- Share what you're working on with neighbors -- Help each other debug issues -- Show off cool features you've created -- Ask each other questions - -### Wrapping Up Your Work - -Before the session ends, take 10 minutes to: - -1. **Commit your changes**: - ```bash - git add . - git commit -m "Work in progress: [what you accomplished]" - git push - ``` - -2. **Document your progress** in README.md: - - What you built or contributed - - What works and what's still in progress - - Interesting challenges you solved - - Next steps you'd like to take - -3. **Reflect** on what you learned: - - What surprised you about AI-assisted development? - - What JupyterLab concepts clicked for you? - - What would you explore next? - -### After the Workshop - -**To continue developing**: -- Add automated tests (`pytest` for Python, `jest` for TypeScript) -- Set up continuous integration (template includes GitHub Actions) -- Write comprehensive documentation -- Create example notebooks showing your extension in use - -**To share your work**: -- Publish to PyPI: `python -m build && twine upload dist/*` -- Add to the [JupyterLab Extensions list](https://github.com/mauhai/awesome-jupyterlab) -- Write a blog post about your development experience -- Present at a local Jupyter meetup - -**To contribute more**: -- Subscribe to issues on extensions you've contributed to -- Join the [Jupyter Zulip chat](https://jupyter.zulipchat.com) for real-time discussions -- Participate in the [Jupyter Discourse forum](https://discourse.jupyter.org/) -- Attend [Jupyter Community Calls](https://jupyter.org/community#calendar) - bring your questions or request code review from experienced developers -- Help others getting started - -:::{tip} The Journey Continues -Building JupyterLab extensions is a skill that develops over time. Every extension you build, every contribution you make, and every bug you debug deepens your understanding. - -The patterns you've learned here - using AI assistance effectively, reading documentation, debugging systematically, and iterating rapidly - apply to all software development, not just JupyterLab. - -Keep building, keep learning, and welcome to the JupyterLab extension developer community! ๐Ÿš€ -::: From 2ae793c353ea8c127d50385030d0d08a5ff3d4b7 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:30:25 -0700 Subject: [PATCH 026/118] move inspiration for chapter 6 to chapter 6 --- 04-materials/02-anatomy-of-extensions.md | 3 --- 04-materials/06-developing-with-ai.md | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/04-materials/02-anatomy-of-extensions.md b/04-materials/02-anatomy-of-extensions.md index 02c07859..86c2c759 100644 --- a/04-materials/02-anatomy-of-extensions.md +++ b/04-materials/02-anatomy-of-extensions.md @@ -52,9 +52,6 @@ This tutorial is inspired by many prior works. photo API is not reliable during the current government shutdown ๐Ÿ˜ญ) * Remove unnecessary use of `conda` in favor of more standard tooling * Structure the activities around in person teaching and exercises - * [Agentic AI Programming for Python Course](https://training.talkpython.fm/courses/agentic-ai-programming-for-python) - by TalkPython['Training'] - * Ideas for teaching AI-assisted coding in 2025 ::: diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 340def4c..55158e41 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -13,6 +13,16 @@ After this module, you will have: - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: +:::{note} Inspired by... +:class: dropdown + +This module's teaching approach is inspired by: + +* [Agentic AI Programming for Python Course](https://training.talkpython.fm/courses/agentic-ai-programming-for-python) + by TalkPython Training + * Ideas for teaching AI-assisted coding in 2025 +::: + ## AI-assisted development in 2025 From 32953093d2508c2ed8c789a3b139dbec6ed61232 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:31:53 -0700 Subject: [PATCH 027/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 55158e41..cdb52eae 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -48,7 +48,7 @@ Before we dive into tools and techniques, let's set the right mindset for workin - Treat errors as learning opportunities for both you and the AI - Don't be afraid to roll back to the beginning and start over if AI doubled down on a wrong path -:::{important} Perfection is Off the Mark +:::{important} Don't expect perfection You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. ::: From 3906bb5f9716dc75a5c0002d3d09dbd848c93d0f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:34:41 -0700 Subject: [PATCH 028/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index cdb52eae..def133df 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -43,7 +43,7 @@ Before we dive into tools and techniques, let's set the right mindset for workin **The right mindset:** - Don't get frustrated by small mistakes โ€” iterate and guide -- Review all generated code (you wouldn't merge a PR without review) +- **Review all generated code** (you wouldn't merge a PR without review) - Ask questions: "Why did you choose this approach?" - Treat errors as learning opportunities for both you and the AI - Don't be afraid to roll back to the beginning and start over if AI doubled down on a wrong path From 1c5d3e1e31c6bf6b3d1dc17d5e84861f6312abb2 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:35:17 -0700 Subject: [PATCH 029/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index def133df..988f00fb 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -81,7 +81,7 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural **Open-source & open-weight models (2025 state-of-the-art):** - **Highly Recommended for Coding:** - - **Qwen3-235B-A22B** (Apache 2.0): 235B params with 22B active, 262K context, exceptional reasoning + - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized - **Qwen3-Coder**: Specialized for code generation tasks From 3a5f0f2fb0baec907e3710ed4b1bb1a8a94ea9dc Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:39:11 -0700 Subject: [PATCH 030/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 988f00fb..82f86f49 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -300,11 +300,6 @@ If you'd prefer to start fresh or didn't complete the anatomy module: cd jupytercon2025-ai-workshop ``` -2. Check out the completed state from the anatomy module: - - ```bash - git checkout exercise-e-step-1 - ``` 3. Install and verify the extension works: From 1e396730bc61501171f61dbf001934e2daab9015 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:39:52 -0700 Subject: [PATCH 031/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 82f86f49..4c68ae6b 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -305,8 +305,8 @@ If you'd prefer to start fresh or didn't complete the anatomy module: ```bash # Create/activate environment - micromamba create -n jupytercon2025-ai python pip nodejs gh "copier~=9.2" jinja2-time - micromamba activate jupytercon2025-ai + micromamba create -n jupytercon2025 python pip nodejs gh "copier~=9.2" jinja2-time + micromamba activate jupytercon2025 # Install the extension in development mode pip install --editable ".[dev,test]" From b0a8918f396cb43e95dd1dcc1097ecdfe93513ea Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:43:31 -0700 Subject: [PATCH 032/118] define some terms in vocabulary --- 03-vocabulary.md | 11 +++++++++++ 04-materials/06-developing-with-ai.md | 10 +++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index a881e0da..723ba89b 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -85,4 +85,15 @@ Launcher : When a new JupyterLab tab is opened, the launcher is displayed. It includes categorized buttons to launch any applications that are registered with the launcher. [See the official docs for more!](https://jupyterlab.readthedocs.io/en/latest/extension/extension_points.html#launcher) + +VRAM +: Video Random Access Memory - dedicated memory on a graphics processing unit (GPU) used for storing image data, textures, and other graphics-related information. +In the context of AI development, VRAM is crucial for loading and running large language models locally. +Typical consumer GPUs have between 4GB to 24GB of VRAM, with high-end models offering more. + +LLM +: Large Language Model - a type of artificial intelligence model trained on vast amounts of text data to understand and generate human-like text. +LLMs can assist with coding tasks like writing code, debugging, refactoring, and explaining complex concepts. +Examples include GPT-5, Claude, and Qwen. +These models are characterized by having billions of parameters and the ability to perform diverse language tasks without task-specific training. ::: diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 4c68ae6b..9a3bf829 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -52,15 +52,15 @@ Before we dive into tools and techniques, let's set the right mindset for workin You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. ::: -### Understanding LLMs (large language models) +### Understanding {term}`LLMs ` (large language models) -AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural networks trained on vast amounts of text and code. These models can: +AI coding assistants are powered by **Large Language Models ({term}`LLMs `)** โ€” neural networks trained on vast amounts of text and code. These models can: - Understand context from your codebase - Generate code snippets based on natural language descriptions - Explain existing code and suggest improvements - Debug errors by analyzing stack traces and code patterns -### Where LLMs live: deployment models +### Where {term}`LLMs ` live: deployment models **Frontier Models (Cloud-Hosted):** - **Examples:** @@ -77,7 +77,7 @@ AI coding assistants are powered by **Large Language Models (LLMs)** โ€” neural - **Examples:** Claude Haiku, Qwen3-30B-A3B (approaching GPT-4o performance), Mistral Small 3.2, Llama 3.3-70B - **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware - **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency -- **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ VRAM) +- **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) **Open-source & open-weight models (2025 state-of-the-art):** - **Highly Recommended for Coding:** @@ -203,7 +203,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - **Best for:** CLI warriors who live in the terminal; Simon Willison's primary coding tool - **Install:** Requires Node.js 18+, then `npm install -g @anthropic/claude-code` -:::{tip} Self-Hosting LLMs +:::{tip} Self-Hosting {term}`LLMs ` If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: ```bash From 4baf2de2ea354b48c8d3b208e20e932a45614233 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:44:10 -0700 Subject: [PATCH 033/118] remove the notion that llm self-corrects --- 04-materials/06-developing-with-ai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 9a3bf829..f203949f 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -141,7 +141,6 @@ Not all AI coding tools are created equal. Understanding the different categorie - Understands your entire codebase - Can execute commands (build, test, format) - Reads documentation and error messages -- Self-corrects when things go wrong - Works iteratively with you **Use cases:** From ed7303be12693d45b3bf9993117b2ef82d7ce5bc Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:47:32 -0700 Subject: [PATCH 034/118] cahnged the pros of open models -- they are catching up to frontier ones --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index f203949f..78f56e10 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -90,7 +90,7 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - **Other Notable Models:** Llama 3.3-70B (Meta), Mistral Small 3.2, Gemma 3 (Google) - **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use - **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) -- **Pros:** Full control, no API costs, data stays local, latest Chinese models often outperform Western alternatives +- **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones - **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) ### Understanding AI coding tool categories From fce1275f38c1750a891c33d641f627bc55211ca9 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:53:26 -0700 Subject: [PATCH 035/118] use better glyphs for AI tool categories --- 04-materials/06-developing-with-ai.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 78f56e10..218aa48c 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -97,7 +97,7 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) Not all AI coding tools are created equal. Understanding the different categories helps you choose the right tool and set appropriate expectations. -#### โŒ Chat-based AI (ChatGPT, Claude web interface) +#### ๐Ÿฅ‰ Chat-based AI (ChatGPT, Claude web interface) **How it works:** - You paste code snippets โ†’ AI gives you code back @@ -117,7 +117,7 @@ Not all AI coding tools are created equal. Understanding the different categorie **Verdict:** Good for learning, frustrating for building features. -#### โš ๏ธ Autocomplete AI (GitHub Copilot basic mode) +#### ๐Ÿฅˆ Autocomplete AI (GitHub Copilot basic mode) **How it works:** - Suggests code as you type (like enhanced IntelliSense) @@ -135,7 +135,7 @@ Not all AI coding tools are created equal. Understanding the different categorie **Verdict:** Some people really prefer this mode (i.e. experienced developers who know when to ignore it, someone who prefers light touch AI interactions), but it can slow down beginners. -#### โœ… Agentic AI (Cursor, Claude Code, Cline, GitHub Copilot Workspace) +#### ๐Ÿฅ‡ Agentic AI (Cursor, Claude Code, Cline, GitHub Copilot Workspace) **How it works:** - Understands your entire codebase From 9956357921fbaf87dad8ead9add4d8c0c02a8b5e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:54:42 -0700 Subject: [PATCH 036/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 218aa48c..11fc649c 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -99,10 +99,7 @@ Not all AI coding tools are created equal. Understanding the different categorie #### ๐Ÿฅ‰ Chat-based AI (ChatGPT, Claude web interface) -**How it works:** -- You paste code snippets โ†’ AI gives you code back -- No context of your full project structure -- Requires manual copy/paste workflow +**How it works:** You paste code snippets โ†’ AI gives you code back **Use cases:** - โœ… Quick one-off questions ("How do I use async/await in TypeScript?") From c5d67ea802c7cc1aa6ac53dfde329a9ae851420f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:55:11 -0700 Subject: [PATCH 037/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 11fc649c..0649e1e7 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -252,7 +252,7 @@ For this workshop: ## Getting started ### Repo -For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from <...>. +For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from [our demo repository](https://github.com/mfisher87/jupytercon2025-developingextensions-demo). In our initial setup, we cloned an official JupyterLab extension template. This template was recently enhanced to include AI-specific configurations and rulesets. From 9e0492882e9de3543fbaffd94bfcbfdeb6010b7c Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:55:39 -0700 Subject: [PATCH 038/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 0649e1e7..7070caef 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -127,7 +127,7 @@ Not all AI coding tools are created equal. Understanding the different categorie **Limitations:** - โš ๏ธ Often 90% right (which means constantly fighting it) - โš ๏ธ Interrupts your flow with suggestions -- โš ๏ธ No understanding of "correctness"โ€”just statistical likelihood +- โš ๏ธ No understanding of "correctness" โ€” just statistical likelihood - โš ๏ธ Can be distracting or helpful depending on your preference. **Verdict:** Some people really prefer this mode (i.e. experienced developers who know when to ignore it, someone who prefers light touch AI interactions), but it can slow down beginners. From baa1054ca773b353b7644666181016967d9855e8 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:55:59 -0700 Subject: [PATCH 039/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 7070caef..da0023ea 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -235,8 +235,8 @@ Most AI tools can be "coerced" into using local models by configuring them to po ### Choosing your tool For this workshop: -- We would start with with **Cursor** -- We would run through the same steps again using **Claude Code** +- We will start with with **Cursor** +- We will run through the same steps again using **Claude Code** :::{note} Further Reading :class: dropdown From d91ded1d94c40a4929225d433b9fd9ba4d3836ad Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:56:21 -0700 Subject: [PATCH 040/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index da0023ea..209e9a1a 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -147,7 +147,7 @@ Not all AI coding tools are created equal. Understanding the different categorie - โœ… Generating tests based on implementation - โœ… Updating documentation alongside code -**Key difference:** It doesn't just suggestโ€”it **acts** like a team member with tools. +**Key difference:** It doesn't just suggest โ€” it **acts** like a team member with tools. **Example workflow:** ``` From 5e5647ed3fe64fc292de518dd8f7bcf231808b81 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:56:55 -0700 Subject: [PATCH 041/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 209e9a1a..760b1609 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -196,7 +196,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - **LLM Options:** - Requires Anthropic API key - Works with Claude Sonnet 4.5, Claude 4 Opus, and other Claude models -- **Best for:** CLI warriors who live in the terminal; Simon Willison's primary coding tool +- **Best for:** CLI warriors who live in the terminal - **Install:** Requires Node.js 18+, then `npm install -g @anthropic/claude-code` :::{tip} Self-Hosting {term}`LLMs ` From 12fc58f1c19ffb06e865668f177b2f066705885f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:57:25 -0700 Subject: [PATCH 042/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 760b1609..33651532 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -323,7 +323,6 @@ We will be using Cursor and Claude Code throughout this tutorial. Please, instal You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. ## Getting started (15 minutes) -- set up repo from previous exercise or checkpoint - install cursor and claude code (existing installed tools are fine to use too) ## Exercise A (15 minutes): Understand AI rules From 54a5e72f036b3af8a1642eee402df9a946441384 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:57:44 -0700 Subject: [PATCH 043/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 33651532..73ee2b4a 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -197,7 +197,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - Requires Anthropic API key - Works with Claude Sonnet 4.5, Claude 4 Opus, and other Claude models - **Best for:** CLI warriors who live in the terminal -- **Install:** Requires Node.js 18+, then `npm install -g @anthropic/claude-code` +- **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` :::{tip} Self-Hosting {term}`LLMs ` If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: From f44479aacdc18c2799e8e55a7874a38b65145c43 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 13:59:15 -0700 Subject: [PATCH 044/118] Update 04-materials/06-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/06-developing-with-ai.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 73ee2b4a..84b60048 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -254,10 +254,11 @@ For this workshop: ### Repo For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from [our demo repository](https://github.com/mfisher87/jupytercon2025-developingextensions-demo). -In our initial setup, we cloned an official JupyterLab extension template. +In {doc}`02-anatomy-of-extensions`, we started off by cloning an official [JupyterLab extension template](https://github.com/jupyterlab/extension-template). This template was recently enhanced to include AI-specific configurations and rulesets. +Then, we built a JupyterLab extension that displays random images with captions from a curated collection. -In {doc}`02-anatomy-of-extensions`, you built a JupyterLab extension that displays random images with captions from a curated collection. Now, we'll use AI to extend this viewer with image editing capabilities. +Now, we'll use AI to extend this viewer with image editing capabilities. ### Option 1: Continue with your own extension From 947990ab2e87be1aafee53c27f6882269f7a7c8a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 14:22:14 -0700 Subject: [PATCH 045/118] mention restoring with git --- 04-materials/06-developing-with-ai.md | 1 + 1 file changed, 1 insertion(+) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 84b60048..e0903715 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -337,6 +337,7 @@ You are totally welcome to use any AI tool you have installed on your computer! - Send a single prompt to Cursor - Basic debugging - Power and peril of one-shot prompts +- git restore . to undo the changes made by the one-shot prompt? ### Exercise C (20 minutes): Product manager framework - Learn how to use structured approach to AI-assisted development From e0b81032baf96a19654c3111657e7f1b647370c2 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 14:53:21 -0700 Subject: [PATCH 046/118] tweaks --- 04-materials/06-developing-with-ai.md | 51 ++++++--------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index e0903715..680778bb 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -28,12 +28,10 @@ This module's teaching approach is inspired by: If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. -### Setting expectations: your AI partner is like a knowledgeable junior developer +### Setting expectations Before we dive into tools and techniques, let's set the right mindset for working with AI. -**Don't expect perfection.** If AI gets 95% of what you need correct, that's incredible โ€” exactly what you'd get from a talented junior developer given the same instructions. - **Key mental model:** Think of your AI assistant as a fast, eager junior developer who: - โœ… Learns quickly and has read tons of documentation (but maybe not the latest stuff) - โœ… Can scaffold code and explore APIs incredibly fast @@ -41,6 +39,11 @@ Before we dive into tools and techniques, let's set the right mindset for workin - โœ… Benefits from clear instructions and iterative feedback - โœ… Gets better with guidance and context +:::{important} Don't expect perfection +If AI gets 95% of what you need correct, that's incredible โ€” exactly what you'd get from a talented junior developer given the same instructions. +You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. +::: + **The right mindset:** - Don't get frustrated by small mistakes โ€” iterate and guide - **Review all generated code** (you wouldn't merge a PR without review) @@ -48,10 +51,6 @@ Before we dive into tools and techniques, let's set the right mindset for workin - Treat errors as learning opportunities for both you and the AI - Don't be afraid to roll back to the beginning and start over if AI doubled down on a wrong path -:::{important} Don't expect perfection -You would never hire a developer and expect absolute perfection in their creative work. Apply the same perspective to AI. If it gets most things right and you refine the rest, that's a massive productivity win. -::: - ### Understanding {term}`LLMs ` (large language models) AI coding assistants are powered by **Large Language Models ({term}`LLMs `)** โ€” neural networks trained on vast amounts of text and code. These models can: @@ -80,14 +79,13 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) **Open-source & open-weight models (2025 state-of-the-art):** -- **Highly Recommended for Coding:** +- **Examples:** - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized - **Qwen3-Coder**: Specialized for code generation tasks - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly -- **Other Notable Models:** Llama 3.3-70B (Meta), Mistral Small 3.2, Gemma 3 (Google) - **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use - **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) - **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones @@ -149,31 +147,13 @@ Not all AI coding tools are created equal. Understanding the different categorie **Key difference:** It doesn't just suggest โ€” it **acts** like a team member with tools. -**Example workflow:** -``` -You: "Add image filters to this widget. Use Pillow on the backend." - -Agentic AI: -1. Reads your widget.ts to understand structure -2. Checks pyproject.toml for dependencies -3. Adds Pillow to dependencies -4. Creates new endpoint in routes.py -5. Updates widget with filter buttons -6. Runs `jlpm build` to verify TypeScript compiles -7. Reports: "Done. Found one type error, fixed it." -``` - -**Verdict:** **This is what we're using in this workshop.** Agentic AI is a fundamentally different experience from chat or autocomplete. - -:::{tip} Focus on Agentic Tools -If you've tried AI coding before and found it frustrating, chances are you were using chat-based AI or basic autocomplete. The techniques in this workshop are designed for **agentic, tool-using AI** that can understand and operate on your full project. -::: +**Verdict:** When building real features, prioritize agentic tools. They understand your codebase, execute commands, and iterate with youโ€”a fundamentally different (and more productive) experience than chat or autocomplete. ### AI IDEs and tools for extension development -Now that you understand the categories, here are the **agentic AI tools** you can use in this workshop, ranging from GUI-first to CLI-native: +Now that you understand the categories, here are the **agentic AI tools** we will use in this workshop: -#### 1. **Cursor** (popular UI-first IDE) +#### 1. **Cursor** - **What it is:** A fork of VS Code with deep AI integration - **LLM Options:** - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) @@ -182,16 +162,7 @@ Now that you understand the categories, here are the **agentic AI tools** you ca - **Best for:** Developers who want a polished, GUI-driven experience - **Download:** [cursor.com](https://cursor.com/) -#### 2. **Cline** (open-source VS Code extension) -- **What it is:** A VS Code extension (formerly Claude Dev) that provides AI-assisted coding -- **LLM Options:** - - Bring your own API key (OpenAI, Anthropic, Google, etc.) - - Supports OpenAI-compatible APIs (e.g., Ollama, LM Studio) - - Fully open-source and community-driven -- **Best for:** Developers who prefer VS Code and want flexibility -- **Install:** Search "Cline" in VS Code extensions or visit [github.com/cline/cline](https://github.com/cline/cline) - -#### 3. **Claude Code** (CLI for power users) +#### 2. **Claude Code** - **What it is:** Command-line interface for Claude, optimized for coding workflows - **LLM Options:** - Requires Anthropic API key From 81f407f0224ebfc806a19d6eca357b4dfa030a7f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 14:57:24 -0700 Subject: [PATCH 047/118] hide ai tool categories under dropdown --- 04-materials/06-developing-with-ai.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index 680778bb..a5e3964f 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -93,7 +93,10 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) ### Understanding AI coding tool categories -Not all AI coding tools are created equal. Understanding the different categories helps you choose the right tool and set appropriate expectations. +Not all AI coding tools are created equal. **In this workshop, we will be working with Agentic AI tools** (like Cursor and Claude Code) because they understand your codebase, execute commands, and iterate with youโ€”a fundamentally different and more productive experience than chat or autocomplete. + +:::{note} Why Agentic AI? Detailed comparison of AI tool categories +:class: dropdown #### ๐Ÿฅ‰ Chat-based AI (ChatGPT, Claude web interface) @@ -148,6 +151,7 @@ Not all AI coding tools are created equal. Understanding the different categorie **Key difference:** It doesn't just suggest โ€” it **acts** like a team member with tools. **Verdict:** When building real features, prioritize agentic tools. They understand your codebase, execute commands, and iterate with youโ€”a fundamentally different (and more productive) experience than chat or autocomplete. +::: ### AI IDEs and tools for extension development From 9e84e3deb2891c997f810132bfe717ea32819a10 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 14:59:30 -0700 Subject: [PATCH 048/118] fix links --- 04-materials/06-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index a5e3964f..d67bd113 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -374,7 +374,7 @@ You've now experienced the complete AI-assisted development workflow: ### Continuing your journey -The next chapter, {doc}`07-independent-work`, provides **independent exploration time** where you can: +The next chapter provides **independent exploration time** where you can: 1. **Build your own extension from scratch** - Using the template and proven project ideas 2. **Contribute to existing extensions** - Give back to the community and learn from production code From e850d93d6a4ca3ea012a11517d9d08f4bcbcab0b Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 15:11:12 -0700 Subject: [PATCH 049/118] fix md link and swap the session indecies --- 04-materials/05-working-on-your-own.md | 2 +- 04-materials/06-developing-with-ai.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/04-materials/05-working-on-your-own.md b/04-materials/05-working-on-your-own.md index 8de30db5..03bd11a7 100644 --- a/04-materials/05-working-on-your-own.md +++ b/04-materials/05-working-on-your-own.md @@ -1,4 +1,4 @@ -# ๐Ÿงฉ 5 - Working on your own extension +# ๐Ÿงฉ 6 - Working on your own extension :::{important} Outcome After completing this module, you will have: diff --git a/04-materials/06-developing-with-ai.md b/04-materials/06-developing-with-ai.md index d67bd113..0a141e5a 100644 --- a/04-materials/06-developing-with-ai.md +++ b/04-materials/06-developing-with-ai.md @@ -1,4 +1,4 @@ -# ๐Ÿค– 6 - Developing extensions with AI assistance +# ๐Ÿค– 5 - Developing extensions with AI assistance ::::{hint} Learning objectives - Use an AI assistant (Cursor, Claude Code, etc.) to build and modify JupyterLab extensions by writing effective prompts with context, constraints, and acceptance criteria; point AI to official docs/APIs and require citations @@ -292,6 +292,7 @@ If you'd prefer to start fresh or didn't complete the anatomy module: Make sure your git tree is clean, there are no unsaved and uncommitted files. This is going to be important later +(ai-tool)= ### AI tool We will be using Cursor and Claude Code throughout this tutorial. Please, install them if you would like to follow. From 6e5be0682fb65c0a5f4d2a7473a2c745083d37a6 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 16:17:59 -0700 Subject: [PATCH 050/118] add cursor installation and account/plan instructions --- 04-materials/05-developing-with-ai.md | 50 +++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 0a141e5a..16400d28 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -159,6 +159,7 @@ Now that you understand the categories, here are the **agentic AI tools** we wil #### 1. **Cursor** - **What it is:** A fork of VS Code with deep AI integration +- **Pricing:** Free plan available (includes one-week Pro trial, limited agent requests, and tab completions). See [cursor.com/pricing](https://cursor.com/pricing) for details. - **LLM Options:** - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) - Bring your own API key (OpenAI, Anthropic, Google, or other providers) @@ -295,12 +296,57 @@ Make sure your git tree is clean, there are no unsaved and uncommitted files. Th (ai-tool)= ### AI tool -We will be using Cursor and Claude Code throughout this tutorial. Please, install them if you would like to follow. +We will be using Cursor and Claude Code throughout this tutorial. Please install them if you would like to follow along. You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. +#### Setting up Cursor + +1. **Download Cursor** + - Visit [cursor.com](https://cursor.com/) and download the installer for your operating system + - Install Cursor like any other application + +2. **Create a Cursor account** + - Launch Cursor + - You'll be prompted to sign in or create an account + - Sign up for a free account (no credit card required) + - The free plan includes a one-week Pro trial, limited agent requests, and tab completions + +3. **Sign up for the free plan** + - During your first launch, Cursor will offer you the free plan with a Pro trial + - Accept the free plan to get started + - See [cursor.com/pricing](https://cursor.com/pricing) for details on what's included + +:::{tip} +The free plan is perfect for this workshop! You'll have access to AI features including: +- Agent mode for building features +- AI chat for asking questions +- Code completions +::: + +#### Setting up Claude Code (optional) + +If you prefer working from the command line, you can also install Claude Code: + +1. **Install Node.js 18+** (if not already installed) + - Visit [nodejs.org](https://nodejs.org/) or use your package manager + +2. **Install Claude Code** + ```bash + npm install --global @anthropic/claude-code + ``` + +3. **Get an Anthropic API key** + - Visit [console.anthropic.com](https://console.anthropic.com/) + - Sign up for an account and generate an API key + - Note: This requires payment setup, unlike Cursor's free plan + ## Getting started (15 minutes) -- install cursor and claude code (existing installed tools are fine to use too) + +By now, you should have: +- โœ… Cursor installed with a free account created +- โœ… (Optional) Claude Code installed with an API key configured +- โœ… Your extension repository open and ready to work with ## Exercise A (15 minutes): Understand AI rules - Inspect AGENTS.md From c7aabe196ee1a13f50ba547e9d338305650be431 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Mon, 27 Oct 2025 21:53:58 -0700 Subject: [PATCH 051/118] clean up AI-assisted development in 2025 section more --- 04-materials/05-developing-with-ai.md | 75 +++++++++++++-------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 16400d28..97176f7a 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -91,6 +91,40 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones - **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) +:::{dropdown} ๐Ÿ’ก Self-Hosting {term}`LLMs ` (Optional) +If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: + +```bash +# Install Ollama +# macOS: Download from https://ollama.com/download/mac +# Windows: Download from https://ollama.com/download/windows +# Linux: +curl -fsSL https://ollama.com/install.sh | sh + +# Download and run a recommended coding model +# For powerful machines (24GB+ VRAM): +ollama run qwen3-coder + +# For laptops/consumer hardware (16GB RAM): +ollama run glm-4.5-air + +# For reasoning tasks: +ollama run deepseek-r1 + +# Use with Cursor/Cline via OpenAI-compatible API +# Point to http://localhost:11434/v1 +``` + +**Model Selection Guide:** +- **Best for coding on powerful hardware:** Qwen3-235B or GLM-4.5 +- **Best for laptops (48GB RAM):** GLM-4.5 Air (quantized) +- **Best for consumer GPUs (16-24GB):** Qwen3-Coder or DeepSeek-R1-Distill +- **Budget option:** GPT-OSS-20B (runs on 16GB RAM) + +Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. +::: + + ### Understanding AI coding tool categories Not all AI coding tools are created equal. **In this workshop, we will be working with Agentic AI tools** (like Cursor and Claude Code) because they understand your codebase, execute commands, and iterate with youโ€”a fundamentally different and more productive experience than chat or autocomplete. @@ -155,7 +189,7 @@ Not all AI coding tools are created equal. **In this workshop, we will be workin ### AI IDEs and tools for extension development -Now that you understand the categories, here are the **agentic AI tools** we will use in this workshop: +Now that you understand the categories, here are the **agentic AI tools** we will use in this workshop. We'll start with **Cursor** to demonstrate the AI-assisted workflow, then repeat key steps using **Claude Code** for a CLI-based approach. Both tools offer similar agentic capabilities, so you can choose whichever fits your preferred workflow after the workshop. #### 1. **Cursor** - **What it is:** A fork of VS Code with deep AI integration @@ -175,45 +209,6 @@ Now that you understand the categories, here are the **agentic AI tools** we wil - **Best for:** CLI warriors who live in the terminal - **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` -:::{tip} Self-Hosting {term}`LLMs ` -If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: - -```bash -# Install Ollama -# macOS: Download from https://ollama.com/download/mac -# Windows: Download from https://ollama.com/download/windows -# Linux: -curl -fsSL https://ollama.com/install.sh | sh - -# Download and run a recommended coding model -# For powerful machines (24GB+ VRAM): -ollama run qwen3-coder - -# For laptops/consumer hardware (16GB RAM): -ollama run glm-4.5-air - -# For reasoning tasks: -ollama run deepseek-r1 - -# Use with Cursor/Cline via OpenAI-compatible API -# Point to http://localhost:11434/v1 -``` - -**Model Selection Guide:** -- **Best for coding on powerful hardware:** Qwen3-235B or GLM-4.5 -- **Best for laptops (48GB RAM):** GLM-4.5 Air (quantized) -- **Best for consumer GPUs (16-24GB):** Qwen3-Coder or DeepSeek-R1-Distill -- **Budget option:** GPT-OSS-20B (runs on 16GB RAM) - -Most AI tools can be "coerced" into using local models by configuring them to point to an OpenAI-compatible API endpoint. -::: - -### Choosing your tool - -For this workshop: -- We will start with with **Cursor** -- We will run through the same steps again using **Claude Code** - :::{note} Further Reading :class: dropdown - [Simon Willison's blog on AI-assisted programming](https://simonwillison.net/tags/ai-assisted-programming/) โ€” Practical insights from a prolific developer From 87b18359591579c060fde058b92edcad250c3b6d Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 11:28:21 -0700 Subject: [PATCH 052/118] clean up unneeded bullet point --- 04-materials/05-developing-with-ai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 97176f7a..ca88cba9 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -20,7 +20,6 @@ This module's teaching approach is inspired by: * [Agentic AI Programming for Python Course](https://training.talkpython.fm/courses/agentic-ai-programming-for-python) by TalkPython Training - * Ideas for teaching AI-assisted coding in 2025 ::: From 5badbaf898440a19ce45bb9dbe520d6b38f9da84 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 12:05:48 -0700 Subject: [PATCH 053/118] rearrange info about models --- 04-materials/05-developing-with-ai.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index ca88cba9..08bff28a 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -61,23 +61,30 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) ### Where {term}`LLMs ` live: deployment models **Frontier Models (Cloud-Hosted):** -- **Examples:** - - **Claude Sonnet 4.5** (Anthropic): Best coding model, $3/$15 per M tokens - - **GPT-5** (OpenAI): Best overall reasoning, $1.25/$10 per M tokens - - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal - - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), $15/$75 per M tokens - **Deployment:** Run on massive server infrastructure by model providers - **Access:** Pay-per-token via API keys +- **Examples:** + - **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per M tokens + - **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per M tokens + - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal + - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per M tokens - **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed - **Cons:** Requires internet connection, ongoing costs, data leaves your machine **Mid-tier and efficient models (cloud or local):** -- **Examples:** Claude Haiku, Qwen3-30B-A3B (approaching GPT-4o performance), Mistral Small 3.2, Llama 3.3-70B - **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware +- **Examples:** + - **Claude Haiku** + - **Qwen3-30B-A3B** (approaching GPT-4o performance), + - **Mistral Small 3.2** + - **Llama 3.3-70B** - **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency - **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) + **Open-source & open-weight models (2025 state-of-the-art):** +- **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use +- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) - **Examples:** - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware @@ -85,8 +92,6 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - **Qwen3-Coder**: Specialized for code generation tasks - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly -- **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use -- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) - **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones - **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) From 56443925d02170e72128df19cef1ef7d1e13ae34 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 12:27:05 -0700 Subject: [PATCH 054/118] update agentic section --- 03-vocabulary.md | 7 +++++++ 04-materials/05-developing-with-ai.md | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index 2b8a22af..85c16317 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -104,4 +104,11 @@ LLM LLMs can assist with coding tasks like writing code, debugging, refactoring, and explaining complex concepts. Examples include GPT-5, Claude, and Qwen. These models are characterized by having billions of parameters and the ability to perform diverse language tasks without task-specific training. + +Agentic AI +: AI systems that can autonomously take actions like an agent, rather than just providing suggestions. +The key differentiator is **tool use**โ€”agentic AI can execute commands (build, test, format), read files, search codebases, and make changes directly. +These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next stepโ€”iterating until the task is complete. +Examples include Cursor, Claude Code, and Cline. +Unlike chat-based or autocomplete AI, agentic tools act like a team member with toolsโ€”they don't just suggest, they do. ::: diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 08bff28a..dd1bda19 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -129,9 +129,9 @@ Most AI tools can be "coerced" into using local models by configuring them to po ::: -### Understanding AI coding tool categories +### AI tools for extension development -Not all AI coding tools are created equal. **In this workshop, we will be working with Agentic AI tools** (like Cursor and Claude Code) because they understand your codebase, execute commands, and iterate with youโ€”a fundamentally different and more productive experience than chat or autocomplete. +Not all AI coding tools are created equal. **In this workshop, we'll use {term}`agentic AI ` tools** that can understand your codebase, execute commands, and iterate with youโ€”a fundamentally different and more productive experience than chat or autocomplete. :::{note} Why Agentic AI? Detailed comparison of AI tool categories :class: dropdown @@ -191,9 +191,7 @@ Not all AI coding tools are created equal. **In this workshop, we will be workin **Verdict:** When building real features, prioritize agentic tools. They understand your codebase, execute commands, and iterate with youโ€”a fundamentally different (and more productive) experience than chat or autocomplete. ::: -### AI IDEs and tools for extension development - -Now that you understand the categories, here are the **agentic AI tools** we will use in this workshop. We'll start with **Cursor** to demonstrate the AI-assisted workflow, then repeat key steps using **Claude Code** for a CLI-based approach. Both tools offer similar agentic capabilities, so you can choose whichever fits your preferred workflow after the workshop. +We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat key steps using **Claude Code** for a CLI-based approach. Both tools offer similar capabilities, so you can choose whichever fits your preferred workflow after the workshop. #### 1. **Cursor** - **What it is:** A fork of VS Code with deep AI integration @@ -203,6 +201,7 @@ Now that you understand the categories, here are the **agentic AI tools** we wil - Bring your own API key (OpenAI, Anthropic, Google, or other providers) - Can be configured to use local models (Qwen3-Coder, GLM-4.5, DeepSeek-R1) via OpenAI-compatible APIs - **Best for:** Developers who want a polished, GUI-driven experience +- **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, $15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) ($10-39/mo), [Cline](https://cline.bot/) (VS Code extension), [Replit Agent](https://replit.com/) (cloud-based) - **Download:** [cursor.com](https://cursor.com/) #### 2. **Claude Code** @@ -211,6 +210,7 @@ Now that you understand the categories, here are the **agentic AI tools** we wil - Requires Anthropic API key - Works with Claude Sonnet 4.5, Claude 4 Opus, and other Claude models - **Best for:** CLI warriors who live in the terminal +- **Alternatives:** [Aider](https://aider.chat/) (open-source, multi-model), [Gemini CLI](https://cloud.google.com/gemini/code-assist) (free tier), Cline CLI mode - **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` :::{note} Further Reading From 89541c4daec6df5e9dcdd937acdbf93b5f4015df Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 12:37:30 -0700 Subject: [PATCH 055/118] more emojis --- 04-materials/05-developing-with-ai.md | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index dd1bda19..9f193ea0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -23,11 +23,11 @@ This module's teaching approach is inspired by: ::: -## AI-assisted development in 2025 +## ๐Ÿš€ AI-assisted development in 2025 If you haven't used AI-assisted development tools yet, you're about to experience a significant shift in how you write code. AI coding assistants can help you explore APIs, generate boilerplate, debug errors, and iterate on features much faster than traditional workflows. -### Setting expectations +### ๐ŸŽฏ Setting expectations Before we dive into tools and techniques, let's set the right mindset for working with AI. @@ -50,7 +50,7 @@ You would never hire a developer and expect absolute perfection in their creativ - Treat errors as learning opportunities for both you and the AI - Don't be afraid to roll back to the beginning and start over if AI doubled down on a wrong path -### Understanding {term}`LLMs ` (large language models) +### ๐Ÿง  Understanding {term}`LLMs ` (large language models) AI coding assistants are powered by **Large Language Models ({term}`LLMs `)** โ€” neural networks trained on vast amounts of text and code. These models can: - Understand context from your codebase @@ -58,7 +58,7 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - Explain existing code and suggest improvements - Debug errors by analyzing stack traces and code patterns -### Where {term}`LLMs ` live: deployment models +### โ˜๏ธ Where {term}`LLMs ` live: deployment models **Frontier Models (Cloud-Hosted):** - **Deployment:** Run on massive server infrastructure by model providers @@ -129,7 +129,7 @@ Most AI tools can be "coerced" into using local models by configuring them to po ::: -### AI tools for extension development +### ๐Ÿ› ๏ธ AI tools for extension development Not all AI coding tools are created equal. **In this workshop, we'll use {term}`agentic AI ` tools** that can understand your codebase, execute commands, and iterate with youโ€”a fundamentally different and more productive experience than chat or autocomplete. @@ -193,7 +193,7 @@ Not all AI coding tools are created equal. **In this workshop, we'll use {term}` We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat key steps using **Claude Code** for a CLI-based approach. Both tools offer similar capabilities, so you can choose whichever fits your preferred workflow after the workshop. -#### 1. **Cursor** +#### 1. ๐Ÿ–ฑ๏ธ **Cursor** - **What it is:** A fork of VS Code with deep AI integration - **Pricing:** Free plan available (includes one-week Pro trial, limited agent requests, and tab completions). See [cursor.com/pricing](https://cursor.com/pricing) for details. - **LLM Options:** @@ -204,7 +204,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, $15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) ($10-39/mo), [Cline](https://cline.bot/) (VS Code extension), [Replit Agent](https://replit.com/) (cloud-based) - **Download:** [cursor.com](https://cursor.com/) -#### 2. **Claude Code** +#### 2. ๐Ÿ’ป **Claude Code** - **What it is:** Command-line interface for Claude, optimized for coding workflows - **LLM Options:** - Requires Anthropic API key @@ -224,9 +224,9 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - [LM Studio](https://lmstudio.ai/) โ€” GUI for running local LLMs ::: -## Getting started +## ๐Ÿ Getting started -### Repo +### ๐Ÿ“ฆ Repo For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from [our demo repository](https://github.com/mfisher87/jupytercon2025-developingextensions-demo). In {doc}`02-anatomy-of-extensions`, we started off by cloning an official [JupyterLab extension template](https://github.com/jupyterlab/extension-template). @@ -235,7 +235,7 @@ Then, we built a JupyterLab extension that displays random images with captions Now, we'll use AI to extend this viewer with image editing capabilities. -### Option 1: Continue with your own extension +### ๐Ÿ”„ Option 1: Continue with your own extension If you completed the anatomy module and want to continue with your extension: @@ -260,7 +260,7 @@ If you completed the anatomy module and want to continue with your extension: 4. Skip to [AI tool](#ai-tool) below. -### Option 2: Clone the finished extension +### ๐Ÿ“ฅ Option 2: Clone the finished extension If you'd prefer to start fresh or didn't complete the anatomy module: @@ -293,13 +293,13 @@ If you'd prefer to start fresh or didn't complete the anatomy module: Make sure your git tree is clean, there are no unsaved and uncommitted files. This is going to be important later (ai-tool)= -### AI tool +### โš™๏ธ AI tool We will be using Cursor and Claude Code throughout this tutorial. Please install them if you would like to follow along. You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. -#### Setting up Cursor +#### ๐ŸŽจ Setting up Cursor 1. **Download Cursor** - Visit [cursor.com](https://cursor.com/) and download the installer for your operating system @@ -323,7 +323,7 @@ The free plan is perfect for this workshop! You'll have access to AI features in - Code completions ::: -#### Setting up Claude Code (optional) +#### โŒจ๏ธ Setting up Claude Code (optional) If you prefer working from the command line, you can also install Claude Code: @@ -340,39 +340,39 @@ If you prefer working from the command line, you can also install Claude Code: - Sign up for an account and generate an API key - Note: This requires payment setup, unlike Cursor's free plan -## Getting started (15 minutes) +## โœ… Getting started (15 minutes) By now, you should have: - โœ… Cursor installed with a free account created - โœ… (Optional) Claude Code installed with an API key configured - โœ… Your extension repository open and ready to work with -## Exercise A (15 minutes): Understand AI rules +## ๐Ÿ“‹ Exercise A (15 minutes): Understand AI rules - Inspect AGENTS.md - Familiarize yourself with UI of Cursor - Choose AI model - Ask AI chat questions to verify that it recognizes the rules -## Exercise B (30 minutes): Build it! +## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! - Discuss our goal briefly (go from image viewer to image editor) - Send a single prompt to Cursor - Basic debugging - Power and peril of one-shot prompts - git restore . to undo the changes made by the one-shot prompt? -### Exercise C (20 minutes): Product manager framework +### ๐Ÿ“Š Exercise C (20 minutes): Product manager framework - Learn how to use structured approach to AI-assisted development - User stories -### Demo: AI from the command line (10 minutes) +### ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) - Demonstrate using Claude Code for development workflow - Study hall is a good time to try it out -## Reflection and next steps +## ๐Ÿค” Reflection and next steps After completing this exercise (via either path), take a moment to reflect: -### Quick reflection (optional) +### ๐Ÿ’ญ Quick reflection (optional) Think about these questions โ€” we'll discuss as a group: @@ -395,9 +395,9 @@ Think about these questions โ€” we'll discuss as a group: We'll share experiences as a group and create a live poll about which techniques resonated most. Your instructor will facilitate this discussion. ::: -### Key takeaways +### ๐Ÿ”‘ Key takeaways -### Challenge extensions (optional) +### ๐ŸŽ“ Challenge extensions (optional) :::{important} ๐Ÿ’พ **Final Git commit and push!** @@ -410,7 +410,7 @@ git push --- -## What's next? +## ๐ŸŽฏ What's next? You've now experienced the complete AI-assisted development workflow: - โœ… Used AI to generate code for new features @@ -418,7 +418,7 @@ You've now experienced the complete AI-assisted development workflow: - โœ… Learned to provide effective context and constraints - โœ… Understood when to accept AI suggestions vs. when to customize -### Continuing your journey +### ๐ŸŒŸ Continuing your journey The next chapter provides **independent exploration time** where you can: From 97228755031fb0659d80297d80828be5c5aec09f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:25:51 -0700 Subject: [PATCH 056/118] update cursor and claude code section --- 04-materials/05-developing-with-ai.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 9f193ea0..1ede14a2 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -195,22 +195,20 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat #### 1. ๐Ÿ–ฑ๏ธ **Cursor** - **What it is:** A fork of VS Code with deep AI integration -- **Pricing:** Free plan available (includes one-week Pro trial, limited agent requests, and tab completions). See [cursor.com/pricing](https://cursor.com/pricing) for details. +- **Pricing:** Free Hobby plan (includes one-week Pro trial, limited agent requests, and limited tab completions). Paid plans \$20-200/mo offer extended/unlimited usage limits and faster response times. See [cursor.com/pricing](https://cursor.com/pricing) for details. - **LLM Options:** - - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription (starting $20/month) - - Bring your own API key (OpenAI, Anthropic, Google, or other providers) - - Can be configured to use local models (Qwen3-Coder, GLM-4.5, DeepSeek-R1) via OpenAI-compatible APIs + - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription - **Best for:** Developers who want a polished, GUI-driven experience -- **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, $15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) ($10-39/mo), [Cline](https://cline.bot/) (VS Code extension), [Replit Agent](https://replit.com/) (cloud-based) +- **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, \$15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) (\$10-39/mo), [Cline](https://cline.bot/) (VS Code extension, free), [Continue](https://continue.dev/) (VS Code/JetBrains extension, free or \$10/mo Teams), [Roo Code](https://roocode.com/) (VS Code extension, free or \$20/mo Pro), [Kilocode](https://kilocode.ai/) (VS Code/JetBrains, free or \$29/user/mo Teams), [Replit Agent](https://replit.com/) (cloud-based) - **Download:** [cursor.com](https://cursor.com/) #### 2. ๐Ÿ’ป **Claude Code** - **What it is:** Command-line interface for Claude, optimized for coding workflows - **LLM Options:** - - Requires Anthropic API key - - Works with Claude Sonnet 4.5, Claude 4 Opus, and other Claude models + - Requires Claude subscription or Anthropic API key. Can also work through cloud providers, like Amazon Bedrock + - Works with Opus 4.1, Sonnet 4.5, Haiku 4.5, and other Claude models - **Best for:** CLI warriors who live in the terminal -- **Alternatives:** [Aider](https://aider.chat/) (open-source, multi-model), [Gemini CLI](https://cloud.google.com/gemini/code-assist) (free tier), Cline CLI mode +- **Alternatives:** [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://ai.google.dev/docs/jules) (async background agent, beta) - **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` :::{note} Further Reading @@ -317,10 +315,7 @@ You are totally welcome to use any AI tool you have installed on your computer! - See [cursor.com/pricing](https://cursor.com/pricing) for details on what's included :::{tip} -The free plan is perfect for this workshop! You'll have access to AI features including: -- Agent mode for building features -- AI chat for asking questions -- Code completions +We recommend you sign up for a free Hobby plan for this workshop! You'll have one week to access agentic AI features. ::: #### โŒจ๏ธ Setting up Claude Code (optional) From dd17f19d34a89164230c4c59499549c37280e115 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:27:58 -0700 Subject: [PATCH 057/118] fix link --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 1ede14a2..7c61c33d 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -208,7 +208,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - Requires Claude subscription or Anthropic API key. Can also work through cloud providers, like Amazon Bedrock - Works with Opus 4.1, Sonnet 4.5, Haiku 4.5, and other Claude models - **Best for:** CLI warriors who live in the terminal -- **Alternatives:** [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://ai.google.dev/docs/jules) (async background agent, beta) +- **Alternatives:** [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) - **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` :::{note} Further Reading From 5249eec16455f6c2906e0464c149437f3029b9a1 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:31:05 -0700 Subject: [PATCH 058/118] Update AI model options in Cursor section to include additional models --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 7c61c33d..75d29d5f 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -197,7 +197,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - **What it is:** A fork of VS Code with deep AI integration - **Pricing:** Free Hobby plan (includes one-week Pro trial, limited agent requests, and limited tab completions). Paid plans \$20-200/mo offer extended/unlimited usage limits and faster response times. See [cursor.com/pricing](https://cursor.com/pricing) for details. - **LLM Options:** - - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro) with Cursor subscription + - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro and more) with Cursor subscription - **Best for:** Developers who want a polished, GUI-driven experience - **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, \$15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) (\$10-39/mo), [Cline](https://cline.bot/) (VS Code extension, free), [Continue](https://continue.dev/) (VS Code/JetBrains extension, free or \$10/mo Teams), [Roo Code](https://roocode.com/) (VS Code extension, free or \$20/mo Pro), [Kilocode](https://kilocode.ai/) (VS Code/JetBrains, free or \$29/user/mo Teams), [Replit Agent](https://replit.com/) (cloud-based) - **Download:** [cursor.com](https://cursor.com/) From 3e53f25c998efa62f9bd7a0127269a7d21e46ab4 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:41:27 -0700 Subject: [PATCH 059/118] reorganize some sections of Getting Started --- 04-materials/05-developing-with-ai.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 75d29d5f..91b0a15a 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -233,7 +233,7 @@ Then, we built a JupyterLab extension that displays random images with captions Now, we'll use AI to extend this viewer with image editing capabilities. -### ๐Ÿ”„ Option 1: Continue with your own extension +#### ๐Ÿ”„ Option 1: Continue with your own extension If you completed the anatomy module and want to continue with your extension: @@ -258,7 +258,7 @@ If you completed the anatomy module and want to continue with your extension: 4. Skip to [AI tool](#ai-tool) below. -### ๐Ÿ“ฅ Option 2: Clone the finished extension +#### ๐Ÿ“ฅ Option 2: Clone the finished extension If you'd prefer to start fresh or didn't complete the anatomy module: @@ -300,25 +300,20 @@ You are totally welcome to use any AI tool you have installed on your computer! #### ๐ŸŽจ Setting up Cursor 1. **Download Cursor** - - Visit [cursor.com](https://cursor.com/) and download the installer for your operating system + - Visit [cursor.com](https://cursor.com/download) and download the installer for your operating system - Install Cursor like any other application 2. **Create a Cursor account** - Launch Cursor - You'll be prompted to sign in or create an account - - Sign up for a free account (no credit card required) - - The free plan includes a one-week Pro trial, limited agent requests, and tab completions - -3. **Sign up for the free plan** - - During your first launch, Cursor will offer you the free plan with a Pro trial - - Accept the free plan to get started - - See [cursor.com/pricing](https://cursor.com/pricing) for details on what's included + - Sign up for a free account + - The [Hobby plan](https://cursor.com/pricing) includes a one-week Pro trial :::{tip} We recommend you sign up for a free Hobby plan for this workshop! You'll have one week to access agentic AI features. ::: -#### โŒจ๏ธ Setting up Claude Code (optional) +#### โŒจ๏ธ Setting up Claude Code If you prefer working from the command line, you can also install Claude Code: From 771746af3d2c39ca7b0ba6b324cd02b2e75c3c28 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:53:23 -0700 Subject: [PATCH 060/118] Point participants to Node.js 22 in their workshop environment Updated Claude Code installation instructions to: - Reference the Node.js 22 already installed in their jupytercon2025 environment - Direct participants to activate their existing environment - Remove outdated reference to Node.js 18+ This ensures participants use the correct, workshop-provided Node.js version rather than attempting to install Node.js separately. --- 04-materials/05-developing-with-ai.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 91b0a15a..263aee39 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -209,7 +209,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - Works with Opus 4.1, Sonnet 4.5, Haiku 4.5, and other Claude models - **Best for:** CLI warriors who live in the terminal - **Alternatives:** [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) -- **Install:** Requires Node.js 18+, then `npm install --global @anthropic/claude-code` +- **Install:** Requires Node.js, then `npm install --global @anthropic/claude-code` :::{note} Further Reading :class: dropdown @@ -275,7 +275,7 @@ If you'd prefer to start fresh or didn't complete the anatomy module: ```bash # Create/activate environment - micromamba create -n jupytercon2025 python pip nodejs gh "copier~=9.2" jinja2-time + micromamba create -n jupytercon2025 python pip nodejs=22 gh "copier~=9.2" jinja2-time micromamba activate jupytercon2025 # Install the extension in development mode @@ -315,10 +315,13 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on #### โŒจ๏ธ Setting up Claude Code -If you prefer working from the command line, you can also install Claude Code: -1. **Install Node.js 18+** (if not already installed) - - Visit [nodejs.org](https://nodejs.org/) or use your package manager +1. **Use your workshop environment** + - You already have Node.js 22 installed in your `jupytercon2025` environment + - Make sure the environment is activated: + ```bash + micromamba activate jupytercon2025 + ``` 2. **Install Claude Code** ```bash From 7a0bd7c0276d3c250afe0d84d0498ae7c39a727e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:56:18 -0700 Subject: [PATCH 061/118] clean up setup section in Getting started --- 04-materials/05-developing-with-ai.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 263aee39..8807c46f 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -315,7 +315,6 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on #### โŒจ๏ธ Setting up Claude Code - 1. **Use your workshop environment** - You already have Node.js 22 installed in your `jupytercon2025` environment - Make sure the environment is activated: @@ -328,16 +327,12 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on npm install --global @anthropic/claude-code ``` -3. **Get an Anthropic API key** - - Visit [console.anthropic.com](https://console.anthropic.com/) - - Sign up for an account and generate an API key - - Note: This requires payment setup, unlike Cursor's free plan - -## โœ… Getting started (15 minutes) +3. **Set up Claude subscription or Anthropic API key** + - More details to be added By now, you should have: - โœ… Cursor installed with a free account created -- โœ… (Optional) Claude Code installed with an API key configured +- โœ… Claude Code installed with an API key configured - โœ… Your extension repository open and ready to work with ## ๐Ÿ“‹ Exercise A (15 minutes): Understand AI rules From f2832f15cc70face3e23f87737deb3ef75aec9f7 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 13:59:33 -0700 Subject: [PATCH 062/118] Reflection section rewrite and small cleanups --- 04-materials/05-developing-with-ai.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 8807c46f..62dd97f9 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -358,9 +358,9 @@ By now, you should have: ## ๐Ÿค” Reflection and next steps -After completing this exercise (via either path), take a moment to reflect: +Phew! ๐Ÿ˜ฎโ€๐Ÿ’จ That was a lot! Now we've completed our exercises let's take a moment to reflect: -### ๐Ÿ’ญ Quick reflection (optional) +### ๐Ÿ’ญ Quick reflection Think about these questions โ€” we'll discuss as a group: @@ -412,7 +412,6 @@ The next chapter provides **independent exploration time** where you can: 1. **Build your own extension from scratch** - Using the template and proven project ideas 2. **Contribute to existing extensions** - Give back to the community and learn from production code -3. **Explore and experiment** - Try multiple small projects to deepen your understanding Choose the path that interests you most, work at your own pace, and instructors will be available to help when you get stuck. From 2471d1daf7bf32b5dbae12c4f435b53dfd06bbc1 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 14:47:44 -0700 Subject: [PATCH 063/118] update AGENTS.md description --- 04-materials/05-developing-with-ai.md | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 62dd97f9..25d0c8db 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -336,10 +336,38 @@ By now, you should have: - โœ… Your extension repository open and ready to work with ## ๐Ÿ“‹ Exercise A (15 minutes): Understand AI rules -- Inspect AGENTS.md -- Familiarize yourself with UI of Cursor -- Choose AI model -- Ask AI chat questions to verify that it recognizes the rules + +Before jumping into code generation, let's set up the "invisible infrastructure" that makes AI assistants work well. This configuration is what makes the difference between mediocre and excellent AI-generated code. + +### AI rules + +AI Rules (also called Cursor Rules, or system prompts) are instructions that automatically precede every conversation with your AI assistant. They're like permanent coaching that guides the AI's behavior. + +**AGENTS.md: The Emerging Standard** + +In 2025, the AI coding ecosystem converged on **AGENTS.md** as the universal format for agent instructions. Emerging as an open standard with OpenAI convening an industry working group and growing adoption across the ecosystem, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. + +**Tool Support Status:** + +| Tool | Support | Format | +|------|---------|--------| +| **Cursor** | โœ… Native | AGENTS.md + .cursor/rules/ | +| **GitHub Copilot** | โœ… Native | AGENTS.md (maintains .github/copilot-instructions.md for backward compatibility) | +| **Zed Editor** | โœ… Native | AGENTS.md | +| **Roo Code** | โœ… Native | AGENTS.md | +| **Claude Code** | โš™๏ธ Via symlink | Create: `ln -s AGENTS.md CLAUDE.md` | +| **Gemini CLI** | โš™๏ธ Via symlink | Create: `ln -s AGENTS.md GEMINI.md` | +| **Aider** | โš™๏ธ Config | Add to .aider.conf.yml: `read: AGENTS.md` | +| **Continue.dev** | โŒ Not yet | Use .continue/rules/ | +| **Cline** | โŒ Not yet | Use .clinerules/rules.md | + +**Key advantage:** Single AGENTS.md file works across your entire team regardless of which AI tool they useโ€”no more maintaining separate .cursorrules, CLAUDE.md, and GEMINI.md files. + +For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. + +### Familiarize yourself with UI of Cursor +### Choose AI model +### Ask AI chat questions to verify that it recognizes the rules ## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! - Discuss our goal briefly (go from image viewer to image editor) From 11d85520050d53cd7afe29899f829ccfff53cda9 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 15:13:31 -0700 Subject: [PATCH 064/118] add partial exercise A --- 04-materials/05-developing-with-ai.md | 99 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 25d0c8db..9fa36531 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -365,9 +365,100 @@ In 2025, the AI coding ecosystem converged on **AGENTS.md** as the universal for For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. -### Familiarize yourself with UI of Cursor -### Choose AI model -### Ask AI chat questions to verify that it recognizes the rules +### What's in Your AGENTS.md File + +1. **Open Cursor app** +2. **Set up cursor cli command** + +3. **Open your extension folder in Cursor** + +```bash +cursor ~/Projects/jupytercon2025-extension-workshop +``` + +4. **Take a moment to get familiar with the interface** + Main area for coding tabs, left side panel for file browser and extensions, right side panel for a chat interface. All should look very similar to VSCode or JupyterLab. + +5. **Check that the rules file exists:** + Look for `AGENTS.md` file in your extension root + + :::{important} + If neither `AGENTS.md` exists, **let an instructor know!** These files contain important context and rules that help AI assistants generate better JupyterLab extension code. + ::: + +6. **Review the ruleset file** + Open `AGENTS.md` + Key sections you'll find: + + **Code Quality Rules:** + - Use structured logging (not `console.log()`) + - Define explicit TypeScript interfaces + - Avoid the `any` type + - Use typeguards over type casts + + **Naming Conventions:** + - Python: `snake_case` for functions, `PascalCase` for classes + - TypeScript: `camelCase` for variables, `PascalCase` for classes + - No mixing styles! + + **Project Structure Guidelines:** + - Frontend code in `src/` + - Backend Python in `/` + - Commands registered in `src/index.ts` + - Routes in `/routes.py` + + **JupyterLab-Specific Patterns:** + - How to register commands + - When to use `ReactWidget` vs `Widget` + - REST API best practices with `ServerConnection` + - State persistence with `IStateDB` + + **Development Workflow:** + - When to run `jlpm build` (TypeScript changes) + - When to restart Jupyter (Python changes) + - How to debug (browser console, terminal logs) + + **Common Pitfalls to Avoid:** + - โŒ Don't use `document.getElementById()` (use JupyterLab APIs) + - โŒ Don't hardcode URLs (use `ServerConnection.makeSettings()`) + - โŒ Don't forget `dispose()` methods (prevents memory leaks) + - โŒ Don't mix `npm` and `jlpm` (use `jlpm` only) + +**Why this matters:** These rules teach AI the JupyterLab patterns **before** it writes any code. Without them, AI might use generic React patterns or wrong APIs. With them, AI generates code that follows JupyterLab conventions from the start. + +### Verify that Cursor recognizes the rules + +1. Open a chat panel and choose Ask Mode + +:::{note} +We recommend to start with Claude Sonnet 4.5 model, but feel free to try other models later! +::: + +2. Ask AI chat questions to verify that it recognizes the rules + + ``` + What package manager should I use for JupyterLab extensions? + ``` + + :::{note} No Visual Indicator + Cursor automatically reads and applies AGENTS.md, but there's **no visual indicator** in the interface showing it's active. + [![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) + ::: + + AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! + +3. Try another test: + ``` + What TypeScript conventions should we follow? + ``` + + AI should mention strict mode, camelCase, avoiding `any` type, etc. + + :::{tip} + **If AI gives wrong answers** (like suggesting `npm` instead of `jlpm`): + - Restart Cursor + - Make sure AGENTS.md is in your project root (not a subdirectory) + - Check that the file is named exactly `AGENTS.md` (case-sensitive) ## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! - Discuss our goal briefly (go from image viewer to image editor) @@ -384,6 +475,8 @@ For this workshop, the official copier template provides AGENTS.md and can creat - Demonstrate using Claude Code for development workflow - Study hall is a good time to try it out + + ## ๐Ÿค” Reflection and next steps Phew! ๐Ÿ˜ฎโ€๐Ÿ’จ That was a lot! Now we've completed our exercises let's take a moment to reflect: From 6f951865b0870a5c713f6019a88f3333c7fa9555 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 15:50:59 -0700 Subject: [PATCH 065/118] add exercise B and C content --- 04-materials/05-developing-with-ai.md | 183 ++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 14 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 9fa36531..a6fac45d 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -428,13 +428,13 @@ cursor ~/Projects/jupytercon2025-extension-workshop ### Verify that Cursor recognizes the rules -1. Open a chat panel and choose Ask Mode +1. Open the Cursor Chat panel (`Cmd/Ctrl + L`) and choose Ask Mode :::{note} We recommend to start with Claude Sonnet 4.5 model, but feel free to try other models later! ::: -2. Ask AI chat questions to verify that it recognizes the rules +2. Ask AI chat questions to verify that it recognizes the rules. Paste the below prompt into the chat ``` What package manager should I use for JupyterLab extensions? @@ -454,28 +454,183 @@ We recommend to start with Claude Sonnet 4.5 model, but feel free to try other m AI should mention strict mode, camelCase, avoiding `any` type, etc. - :::{tip} - **If AI gives wrong answers** (like suggesting `npm` instead of `jlpm`): + :::{tip} If AI gives wrong answers - Restart Cursor - Make sure AGENTS.md is in your project root (not a subdirectory) - Check that the file is named exactly `AGENTS.md` (case-sensitive) + ::: ## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! -- Discuss our goal briefly (go from image viewer to image editor) -- Send a single prompt to Cursor -- Basic debugging -- Power and peril of one-shot prompts -- git restore . to undo the changes made by the one-shot prompt? +### Understanding your starting point + +Before we extend the functionality, a quick reminder on what the extension currently does: + +**Current Features:** +- โœ… Displays random images from a curated collection +- โœ… Shows captions for each image +- โœ… Refresh button to load a new random image +- โœ… Layout restoration (widget persists across JupyterLab sessions) + +%**Frontend (TypeScript):** +%- `src/index.ts`: Plugin registration, command definitions, launcher/palette integration +%- `src/widget.ts`: Main UI widget that displays images and captions +%- `src/request.ts`: Utility for communicating with the backend API +% +%**Backend (Python):** +%- `jupytercon2025_extension_workshop/routes.py`: REST API handlers +%- `jupytercon2025_extension_workshop/images_and_captions.py`: Image metadata +%- `jupytercon2025_extension_workshop/images/`: Image files on disk + +### Goal: Add Image Editing Capabilities + +We'll use AI to extend this viewer with basic image editing features. This exercise demonstrates: +- Power and limitation of a single prompt approach to building features +- The iterative development cycle: plan โ†’ implement โ†’ test โ†’ refine +- How to debug and fix issues with AI assistance +- Monitoring AI context + +### Power and peril of one-shot prompts + +Before we dive into our structured approach, let's witness what modern AI can accomplish with a single, well-crafted prompt. This demonstration shows both the impressive capabilities and important limitations of AI-driven development. + +With the right context and a detailed prompt, AI can build complete features in minutes. Here's a prompt that could generate our entire image editing extension: + +``` +Extend this image viewer extension to add image editing capabilities: + +Add editing controls to the widget: +- Buttons for filters: grayscale, sepia, blur, sharpen +- Basic crop functionality (50% crop from center) +- Brightness/contrast adjustments (slider controls) +- Save edited image back to disk + +Use Pillow (PIL) on the backend to process images. The backend should: +- Accept the image filename and editing operation via REST API +- Apply the transformation using appropriate Pillow methods +- Return the processed image to the frontend as base64-encoded data + +The frontend should: +- Update the displayed image immediately after each edit +- Show the current filter/transformation applied +- Allow chaining multiple edits before saving + +Technical requirements: +- Add Pillow to the Python dependencies +- Create a new REST endpoint `/edit-image` in routes.py +- Add filter buttons to the widget toolbar +- Maintain the existing refresh functionality +``` + + +### Basic debugging +### git restore . to undo the changes made by the one-shot prompt? ### ๐Ÿ“Š Exercise C (20 minutes): Product manager framework -- Learn how to use structured approach to AI-assisted development -- User stories -### ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) -- Demonstrate using Claude Code for development workflow -- Study hall is a good time to try it out +#### The better way: structured, iterative development + +While one-shot prompts are impressive for demos, professional development requires a more thoughtful approach. We'll now proceed with a structured workflow that: + +1. **Plans before coding** - Understand the architecture first +2. **Implements in phases** - Build incrementally with checkpoints +3. **Reviews each step** - Catch issues early +4. **Maintains control** - You make the key decisions +This takes longer but results in: +- โœ… Code you understand and can maintain +- โœ… Architecture that fits your needs +- โœ… Proper error handling and edge cases +- โœ… Learning opportunities at each step + +#### The rise of the Product Manager mindset +AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. + +Before generating any code, we'll have AI create a phased implementation plan. This: +- โœ… Keeps AI focused and prevents scope creep +- โœ… Gives you a roadmap to refer back to +- โœ… Makes it easy to resume work across sessions +- โœ… Documents architectural decisions + +1. **Create a plans directory:** + + ```bash + mkdir plans + ``` +2. **Start a new chat in Cursor** and use this prompt: + + ```` + I'm extending a JupyterLab image viewer to add image editing capabilities. + + Please create a detailed implementation plan and save it to plans/image-editing-feature.md + + **Requirements:** + - Add filter buttons (grayscale, sepia, blur, sharpen) + - Use Pillow (PIL) on the backend for processing + - New REST endpoint `/edit-image` for transformations + - Update frontend to display edited images immediately + - Basic crop functionality (50% from center) + - Brightness/contrast sliders + - Save edited image back to disk + + **DO NOT WRITE CODE YET.** Create a phased plan with: + + **Phase 1: MVP** + - Basic filter buttons (grayscale, sepia) + - Backend endpoint scaffolding + - Frontend display of processed images + + **Phase 2: Advanced Filters** + - Blur and sharpen filters + - Crop functionality + - Brightness/contrast adjustments + + **Phase 3: Polish** + - Save functionality + - Undo/redo buttons + - Loading states and error handling + + For each phase, list: + - Specific files to create/modify + - Python/TypeScript dependencies needed + - Testing approach + - Potential issues to watch for + + Save this plan to plans/image-editing-feature.md + ```` + +3. **Review the plan:** + - Open `plans/image-editing-feature.md` + - Read through each phase + - Ask questions if anything is unclear: + ``` + In Phase 1, why did you choose to handle images as base64? + What are the alternatives? + ``` + +4. **Commit the plan:** + + ```bash + git add plans/image-editing-feature.md + git commit -m "Add implementation plan for image editing feature" + ``` + +**Why this matters:** You now have a versioned plan that AI (and you) can reference. As you work through phases, AI will stay focused on the current step. + +:::{tip} Planning Mode vs. File-Based Plans +Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-based plans are: +- โœ… Versioned in Git +- โœ… Synced across machines +- โœ… Reviewable and editable +- โœ… Persistent across sessions + +Always save plans to files: `plans/*.md` +::: + + + +### ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) +- Demonstrate using Claude Code for development workflow ## ๐Ÿค” Reflection and next steps From 82ea166a86b4ac7a724baa55f264a3882126848f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 16:05:59 -0700 Subject: [PATCH 066/118] iterate on first two prompts --- 04-materials/05-developing-with-ai.md | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index a6fac45d..5cfce123 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -437,7 +437,7 @@ We recommend to start with Claude Sonnet 4.5 model, but feel free to try other m 2. Ask AI chat questions to verify that it recognizes the rules. Paste the below prompt into the chat ``` - What package manager should I use for JupyterLab extensions? + What package manager should I use for JupyterLab extension frontend? ``` :::{note} No Visual Indicator @@ -447,19 +447,32 @@ We recommend to start with Claude Sonnet 4.5 model, but feel free to try other m AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! -3. Try another test: - ``` - What TypeScript conventions should we follow? - ``` - - AI should mention strict mode, camelCase, avoiding `any` type, etc. - :::{tip} If AI gives wrong answers - Restart Cursor - Make sure AGENTS.md is in your project root (not a subdirectory) - Check that the file is named exactly `AGENTS.md` (case-sensitive) ::: +3. Get ready for the development. Start a new chat and choose Agent Mode: + ``` + Prepare my extension for development: + 1. Check that I am using the correct environment `jupytercon2025` + 2. Check that my environment have JupyterLab and nodejs installed + 3. Check that TypeScript is configured correctly + 4. Verify extension is installed + 5. Build my extension + ``` + + AI should respond with: + - Checking your enviro + - Mentions rules from AGENTS.md (strict mode, camelCase, etc.) + - Shows awareness of JupyterLab patterns + +:::{tip} You're Ready! +Once AI can reference your project structure, coding rules, and build your project, you're set up for success. This configuration is what separates "AI that generates random code" from "AI that writes code that matches your project's patterns." +::: + + ## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! ### Understanding your starting point @@ -522,6 +535,7 @@ Technical requirements: ``` + ### Basic debugging ### git restore . to undo the changes made by the one-shot prompt? From 90b74f4953f69334f7298077e21b913f3624a23a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 16:14:24 -0700 Subject: [PATCH 067/118] way more details on model selection --- 04-materials/05-developing-with-ai.md | 49 ++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 5cfce123..48a56c54 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -431,7 +431,54 @@ cursor ~/Projects/jupytercon2025-extension-workshop 1. Open the Cursor Chat panel (`Cmd/Ctrl + L`) and choose Ask Mode :::{note} -We recommend to start with Claude Sonnet 4.5 model, but feel free to try other models later! +**โš ๏ธ Avoid "Auto" mode** โ€” it picks the cheapest model, not the best one. + +**Recommended Models:** +- **GPT-5** for planning and reasoning tasks +- **Claude Sonnet 4.5** for coding tasks + +These models provide the best balance of quality and capability for JupyterLab extension development. +::: + +:::{dropdown} More Details on Model Selection +:icon: info + +**Model selection impacts both quality and cost.** + +1. **Enable model selector:** + - Settings โ†’ Cursor Settings โ†’ General + - Find "Usage Summary" โ†’ Set to "Always" (not "Auto") + - This shows your credit usage at bottom of chat panel + +2. **Choose models strategically:** + + | Task | Recommended Model | Why | + |------|------------------|-----| + | Planning & Reasoning | GPT-5 or Claude Sonnet 4.5 (Thinking) | GPT-5 leads reasoning benchmarks; Claude excellent for extended thinking | + | Coding (Best Overall) | Claude Sonnet 4.5 | "Best coding model in the world" per Simon Willison; 99.29% safety rate | + | Long Coding Sessions | Claude Opus 4.1 | Sustains focus for 30+ hours, ideal for large refactors and multi-step tasks | + | Speed & Long Context | Gemini 2.5 Pro | 1M token context, sub-second streaming, best latency | + | Quick fixes | Claude Haiku 4.5 or GPT-5 Mini | Faster, cheaper for simple edits and routine tasks | + | Local Development | GLM-4.5 Air, Qwen3-235B, or DeepSeek-R1 | Best open models for self-hosting on consumer hardware | + | Avoid | Auto | Cursor picks cheapest, not best | + +3. **Watch your context usage:** + - Look for percentage in chat (e.g., "23.4%") + - Keep under 50% for best results + - Above 70%? Start a new chat + +4. **Monitor credits:** + - Check `cursor.com/settings` โ†’ Usage + - Typical costs: Planning ($1-2), Implementation ($0.30-0.50) + +**Start Big, Optimize Later** +For planning and architecture, always use the highest-quality model available: +- **Cloud:** GPT-5 for reasoning, Claude Sonnet 4.5 for coding +- **Self-hosted:** DeepSeek-R1 or Qwen3-235B-A22B + +You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM-4.5 Air) for routine edits, but don't skimp on the thinking phase. +::: + ::: 2. Ask AI chat questions to verify that it recognizes the rules. Paste the below prompt into the chat From 70146a40ccf12dc400c062cbbddeab8c8cac61df Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 16:35:45 -0700 Subject: [PATCH 068/118] add more Exercise B content --- 04-materials/05-developing-with-ai.md | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 48a56c54..bf04e4c1 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -581,9 +581,80 @@ Technical requirements: - Maintain the existing refresh functionality ``` +### What Happens with This Prompt? +When you give this prompt to an AI agent like Cursor or Claude Code, it will typically: + +1. **Analyze your existing codebase** to understand the current structure +2. **Make architectural decisions** about implementation patterns +3. **Generate 200+ lines of code** across multiple files +4. **Update dependencies** in pyproject.toml +5. **Create new endpoints** in your backend +6. **Modify the frontend widget** with new UI controls +7. **Run build commands** to verify everything compiles + +Send the prompt and watch as it generates the entire feature. **In about 2-3 minutes**, you will have a fully functional image editor! + +**Review the generated code** +- Cursor will suggest changes across multiple files +- Read through each change carefully +- Notice the architectural choices +- Look for comments explaining the approach +- Check if dependencies were added correctly + +**Accept or modify** the suggestions +- Click "Accept" to apply all changes +- Or click individual files to review and edit before accepting + +**Test the functionality:** +```bash +jlpm build +jupyter lab +``` + +**Test the new features**: +- Open the image viewer widget +- Try each filter button +- Check the browser console for errors (`F12` or `Cmd+Option+I`) +- Check the terminal running `jupyter lab` for Python errors +- Find at least 3 decisions you might have made differently + +### The Hidden Cost: Decisions Made Without You + +While impressive, this one-shot approach makes numerous decisions on your behalf: + +**Architecture Decisions:** +- โ“ Base64 encoding vs. temporary file URLs? +- โ“ Stateful vs. stateless image processing? +- โ“ Where to store edited images? + +**UI/UX Decisions:** +- โ“ Button placement and styling +- โ“ Slider ranges and defaults +- โ“ Error message presentation +- โ“ Loading state indicators + +**Technical Implementation:** +- โ“ PIL filter parameters (blur radius, sharpen intensity) +- โ“ Image format handling (JPEG quality, PNG transparency) +- โ“ Memory management for large images +- โ“ Caching strategy for processed images + +**Code Quality:** +- โ“ Error handling approach +- โ“ TypeScript type definitions +- โ“ Test coverage + +:::{warning} The Product Manager Trap +When you use one-shot prompts, you're essentially saying: "AI, you be the product manager, architect, and developer all at once." + +This works great for prototypes, but in production code, you need to understand and own these decisions. +::: ### Basic debugging + +**The debugging workflow:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. + ### git restore . to undo the changes made by the one-shot prompt? ### ๐Ÿ“Š Exercise C (20 minutes): Product manager framework From 72fd49e2f8caf306e3972dbd64c36a7f09efc3ba Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 18:29:06 -0700 Subject: [PATCH 069/118] small edit --- 04-materials/05-developing-with-ai.md | 1 + 1 file changed, 1 insertion(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index bf04e4c1..1f0ff1f2 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -667,6 +667,7 @@ While one-shot prompts are impressive for demos, professional development requir 2. **Implements in phases** - Build incrementally with checkpoints 3. **Reviews each step** - Catch issues early 4. **Maintains control** - You make the key decisions +5. **Manages AI context** - Start with fresh chats for each phase This takes longer but results in: - โœ… Code you understand and can maintain From 98cd7e5e76faae032472a0a609ee815843980e39 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 19:07:09 -0700 Subject: [PATCH 070/118] add a new step to modify AI rules clean up incorrect MyST formatting --- 04-materials/05-developing-with-ai.md | 46 ++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 1f0ff1f2..29a18a8e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -367,6 +367,16 @@ For this workshop, the official copier template provides AGENTS.md and can creat ### What's in Your AGENTS.md File +:::{tip} The Magic Behind Good AI Code +When you see AI generate well-structured JupyterLab code in exercises, it's not magic - it's reading your AGENTS.md file! These rules are why AI knows to: +- Use `jlpm` instead of `npm` +- Put commands in `src/index.ts` +- Extend the right base classes +- Follow JupyterLab naming patterns + +Without proper AI context that AGENTS.md provides, we observed AI generating generic code, or using the wrong package manager or thinking too much about source vs. prebuilt extensions. +::: + 1. **Open Cursor app** 2. **Set up cursor cli command** @@ -426,6 +436,38 @@ cursor ~/Projects/jupytercon2025-extension-workshop **Why this matters:** These rules teach AI the JupyterLab patterns **before** it writes any code. Without them, AI might use generic React patterns or wrong APIs. With them, AI generates code that follows JupyterLab conventions from the start. +### Customize your AGENTS.md + +It can be helpful to modify the provided generic "JupyterLab extension" AI rules to include your favorite tools, package managers, and conventions. + +Let's modify the rules to include the package manager we are using and the environment name, +so that the Cursor would have an easier time building our extension. + +Open your `AGENTS.md` file and find the "Environment Activation (CRITICAL)" section. Modify it to specify our workshop environment: + +```diff + ### Environment Activation (CRITICAL) + + **Before ANY command**, ensure you're in the correct environment: + +-```bash +-# For conda/mamba/micromamba (replace `conda` with `mamba` or `micromamba` depending on the prompter's preferred tool): +-conda activate +- +-# For venv: +-source /bin/activate # On macOS/Linux +-\Scripts\activate.bat # On Windows +-``` ++Use micromamba: ++```bash ++micromamba activate jupytercon2025 ++``` + + **All `jlpm`, `pip`, and `jupyter` commands MUST run within the activated environment.** +``` + +This tells the AI assistant to use `micromamba` with the `jupytercon2025` environment that we're using in this workshop, making it easier for the AI to run build commands correctly. + ### Verify that Cursor recognizes the rules 1. Open the Cursor Chat panel (`Cmd/Ctrl + L`) and choose Ask Mode @@ -441,8 +483,6 @@ These models provide the best balance of quality and capability for JupyterLab e ::: :::{dropdown} More Details on Model Selection -:icon: info - **Model selection impacts both quality and cost.** 1. **Enable model selector:** @@ -479,8 +519,6 @@ For planning and architecture, always use the highest-quality model available: You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM-4.5 Air) for routine edits, but don't skimp on the thinking phase. ::: -::: - 2. Ask AI chat questions to verify that it recognizes the rules. Paste the below prompt into the chat ``` From 0b6be1c1b6a3aad6b50baa1b5b6219613ac3b68e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 19:50:17 -0700 Subject: [PATCH 071/118] Add cleanup commands at the end of exercise --- 04-materials/05-developing-with-ai.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 29a18a8e..e2c5ed93 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -693,7 +693,17 @@ This works great for prototypes, but in production code, you need to understand **The debugging workflow:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. -### git restore . to undo the changes made by the one-shot prompt? +### Roll back when done + +To undo all changes made by the one-shot prompt: + +```bash +# Discard all changes to tracked files +git restore . + +# Remove any new untracked files created by the AI +git clean -fd +``` ### ๐Ÿ“Š Exercise C (20 minutes): Product manager framework From 584573e34d1c0c1337e19b00b6e41789047414d1 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 20:03:42 -0700 Subject: [PATCH 072/118] add final step for Exercise A and rough draft of Claude Code demo --- 04-materials/05-developing-with-ai.md | 62 ++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index e2c5ed93..3e4b6627 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -809,9 +809,60 @@ Always save plans to files: `plans/*.md` ::: +:::{important} ๐Ÿ’พ **Final Git commit and push!** +```bash +git add . +git commit -m "Complete Exercise 1: Image editing with AI assistance" +git push +``` +::: ### ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) -- Demonstrate using Claude Code for development workflow + +1. **Start an interactive session**: + + ```bash + claude-code + ``` + +2. **Provide context** by referencing files in your prompt: + + ``` + I'm working on a JupyterLab extension. Please read these files for context: + - src/widget.ts + - jupytercon2025_extension_workshop/routes.py + - AGENTS.md + - package.json + - pyproject.toml + + [Then paste the main prompt about adding image editing capabilities] + ``` + +3. **Review and apply changes**: + - Claude Code will show diffs for each file + - Type `y` to accept, `n` to skip, or `e` to edit + - Changes are applied directly to your files + +### Claude Code Tips + +**Run commands without leaving the chat:** + +``` +Can you also run `jlpm build` to verify this compiles? +``` + +**Ask for explanations:** + +``` +Before you change the code, explain how Pillow's ImageFilter.BLUR works +and why you're choosing this approach. +``` + +**Request tests:** + +``` +Generate pytest tests for the new /edit-image endpoint. +``` ## ๐Ÿค” Reflection and next steps @@ -844,15 +895,6 @@ We'll share experiences as a group and create a live poll about which techniques ### ๐ŸŽ“ Challenge extensions (optional) - -:::{important} ๐Ÿ’พ **Final Git commit and push!** -```bash -git add . -git commit -m "Complete Exercise 1: Image editing with AI assistance" -git push -``` -::: - --- ## ๐ŸŽฏ What's next? From b9b201febdd8dc955b194c14847f3eeac1e6770d Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 20:06:48 -0700 Subject: [PATCH 073/118] update heading hierarchy --- 04-materials/05-developing-with-ai.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 3e4b6627..77d317b7 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -705,9 +705,9 @@ git restore . git clean -fd ``` -### ๐Ÿ“Š Exercise C (20 minutes): Product manager framework +## ๐Ÿ“Š Exercise C (20 minutes): Product manager framework -#### The better way: structured, iterative development +### The better way: structured, iterative development While one-shot prompts are impressive for demos, professional development requires a more thoughtful approach. We'll now proceed with a structured workflow that: @@ -723,7 +723,7 @@ This takes longer but results in: - โœ… Proper error handling and edge cases - โœ… Learning opportunities at each step -#### The rise of the Product Manager mindset +### The rise of the Product Manager mindset AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. Before generating any code, we'll have AI create a phased implementation plan. This: @@ -809,6 +809,8 @@ Always save plans to files: `plans/*.md` ::: + + :::{important} ๐Ÿ’พ **Final Git commit and push!** ```bash git add . @@ -817,7 +819,7 @@ git push ``` ::: -### ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) +## ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) 1. **Start an interactive session**: From 6748848ad459dd068686640099c68660e9f2af27 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 22:42:26 -0700 Subject: [PATCH 074/118] add phase by phase implementation steps --- 04-materials/05-developing-with-ai.md | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 77d317b7..5f5c9011 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -806,10 +806,44 @@ Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-ba - โœ… Persistent across sessions Always save plans to files: `plans/*.md` + +### Implement Phase by Phase + +:::{tip} +**Context window management:** Instead of one long chat for everything, start fresh for each phase. ::: +1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + K`) + +2. **Reference the plan:** + + ``` + We are ready for Phase 1 of @plans/image-editing-feature.md + + Please implement the MVP: basic grayscale and sepia filters with + backend endpoint and frontend display. + ``` + + Note the `@plans/...` syntax tells AI to read that specific file. +3. **Review changes in Source Control** (keep this panel open!) + +4. **Commit after Phase 1 works:** + + ```bash + git add . + git commit -m "Phase 1: Add basic image filters (grayscale, sepia)" + ``` + +5. **Start ANOTHER fresh chat for Phase 2:** + + ``` + We are ready for Phase 2 of @plans/image-editing-feature.md + + Phase 1 is complete. Now implement advanced filters (blur, sharpen, crop). + ``` +**Why new chats?** Keeps context window small (<50%), prevents AI confusion, reduces costs. File-based plans allow us to start new chats and still keep the required context. :::{important} ๐Ÿ’พ **Final Git commit and push!** ```bash From cfd58f7264893595113eb6476760d10850e4cf06 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Tue, 28 Oct 2025 22:53:29 -0700 Subject: [PATCH 075/118] add context managing tips --- 04-materials/05-developing-with-ai.md | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 5f5c9011..95c1a03e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -809,8 +809,25 @@ Always save plans to files: `plans/*.md` ### Implement Phase by Phase -:::{tip} -**Context window management:** Instead of one long chat for everything, start fresh for each phase. +:::{tip} Managing Context and Costs +Instead of one long chat for everything, start fresh for each phase. + +Why new chats? It has do with LLMs context window. Saturating the context window leads to AI confusion and runs up the costs. File-based plans allow us to start new chats and still keep the required context. + +As you work through phases, keep an eye on these metrics: + +**Context window percentage** (shown in Cursor chat): +- **< 30%:** Healthy, plenty of room +- **30-50%:** Good, AI still focused +- **50-70%:** Getting crowded, consider new chat soon +- **> 70%:** Start new chat immediately + +**When to start a new chat:** +- โœ… Completed a phase (Phase 1 โ†’ Phase 2) +- โœ… Switching focus areas (backend โ†’ frontend) +- โœ… Context above 50% +- โœ… AI seems confused or gives inconsistent answers +- โœ… Major refactoring complete ::: 1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + K`) @@ -843,7 +860,14 @@ Always save plans to files: `plans/*.md` Phase 1 is complete. Now implement advanced filters (blur, sharpen, crop). ``` -**Why new chats?** Keeps context window small (<50%), prevents AI confusion, reduces costs. File-based plans allow us to start new chats and still keep the required context. +6. **Commit after Phase 2 works:** + + ```bash + git add . + git commit -m "Phase 2: Add advanced filters (blur, sharpen, crop)" + ``` + +::: Adopt a "product manager" mindset :::{important} ๐Ÿ’พ **Final Git commit and push!** ```bash From 9a1ab25017d3667b11bf304307dcdd3957eb4b7a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 09:37:19 -0700 Subject: [PATCH 076/118] improve option 2 for getting started --- 04-materials/02-anatomy-of-extensions.md | 1 + 04-materials/05-developing-with-ai.md | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/04-materials/02-anatomy-of-extensions.md b/04-materials/02-anatomy-of-extensions.md index fc2669ba..bf59d736 100644 --- a/04-materials/02-anatomy-of-extensions.md +++ b/04-materials/02-anatomy-of-extensions.md @@ -107,6 +107,7 @@ micromamba install python pip nodejs=22 gh "copier~=9.2" jinja2-time ::: +(gh-auth-setup)= ### ๐Ÿ”ง Create a GitHub repository and clone it locally 0. Change to the parent directory where you want to work, e.g. diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 95c1a03e..48119b71 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -258,18 +258,24 @@ If you completed the anatomy module and want to continue with your extension: 4. Skip to [AI tool](#ai-tool) below. -#### ๐Ÿ“ฅ Option 2: Clone the finished extension +#### ๐Ÿ“ฅ Option 2: Fork the finished extension (recommended) If you'd prefer to start fresh or didn't complete the anatomy module: -1. Clone the demo repository: +1. Ensure you are authenticated with GitHub CLI: + + See {ref}`Chapter 2 โ†’ Create a GitHub repository and clone it locally ` (steps 1โ€“2) for GitHub CLI authentication and `gh auth setup-git`. + +2. Fork the demo repository to your GitHub account and clone it locally: ```bash cd ~/Projects - git clone https://github.com/jupytercon/jupytercon2025-developingextensions-demo.git jupytercon2025-ai-workshop - cd jupytercon2025-ai-workshop + gh repo fork jupytercon/jupytercon2025-developingextensions-demo --clone --remote + cd jupytercon2025-developingextensions-demo ``` + This sets `origin` to your fork and `upstream` to the original, so you can commit and push to your fork while still pulling updates from the source repo. + 3. Install and verify the extension works: From 5b13af3192d09f541773b54a9476487ce77648ad Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 09:49:21 -0700 Subject: [PATCH 077/118] include link to agent.md website --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 48119b71..f36b185e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -351,7 +351,7 @@ AI Rules (also called Cursor Rules, or system prompts) are instructions that aut **AGENTS.md: The Emerging Standard** -In 2025, the AI coding ecosystem converged on **AGENTS.md** as the universal format for agent instructions. Emerging as an open standard with OpenAI convening an industry working group and growing adoption across the ecosystem, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. +In 2025, the AI coding ecosystem converged on [**AGENTS.md**](https://agents.md/) as the universal format for agent instructions. Emerging as an open standard with OpenAI convening an industry working group and growing adoption across the ecosystem, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. **Tool Support Status:** From 85b03a40ab855231cfb3ffad1e900d600be7fc26 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 09:54:46 -0700 Subject: [PATCH 078/118] temporary remove a failing link --- 01-prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01-prerequisites.md b/01-prerequisites.md index 64a2473a..72a84943 100644 --- a/01-prerequisites.md +++ b/01-prerequisites.md @@ -31,7 +31,7 @@ the `gh auth login` command. Feel free to use your favorite. Any command-line or graphical editor is fine! -[VSCode](https://code.visualstudio.com/) is a safe choice. +VSCode is a safe choice. For the AI-enabled development portion, [Cursor](https://cursor.com/) is another good choice! From ba5f70667078e57f059481cb2b1fc741c80c14d9 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 09:59:01 -0700 Subject: [PATCH 079/118] fix step references for github auth --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index f36b185e..84e894cb 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -264,7 +264,7 @@ If you'd prefer to start fresh or didn't complete the anatomy module: 1. Ensure you are authenticated with GitHub CLI: - See {ref}`Chapter 2 โ†’ Create a GitHub repository and clone it locally ` (steps 1โ€“2) for GitHub CLI authentication and `gh auth setup-git`. + See {ref}`Chapter 2 โ†’ Create a GitHub repository and clone it locally ` (steps 2-3) for GitHub CLI authentication and `gh auth setup-git`. 2. Fork the demo repository to your GitHub account and clone it locally: From f7eebad06bb35dd5ff05c7b4024efc9deaf8f04a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 14:26:50 -0700 Subject: [PATCH 080/118] update outcomes and objectives --- 04-materials/05-developing-with-ai.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 84e894cb..e3393546 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -1,15 +1,15 @@ # ๐Ÿค– 5 - Developing extensions with AI assistance ::::{hint} Learning objectives -- Use an AI assistant (Cursor, Claude Code, etc.) to build and modify JupyterLab extensions by writing effective prompts with context, constraints, and acceptance criteria; point AI to official docs/APIs and require citations -- Generate and refine extension code (TypeScript/React UI, commands, settings schemas, Python endpoints) with AI guidance; run build/lint/tests and interpret errors to request focused fixes -- Configure and customize a local AI ruleset (AGENTS.md) for your development workflow; understand privacy, licensing, and safety considerations when sharing code with AI tools +- Use agentic AI tools (Cursor and Claude Code) to build and evolve JupyterLab extensions using productโ€‘manager style prompts with context, constraints, and acceptance criteria +- Configure and verify AGENTS.md and environment conventions so tools follow project patterns +- Implement and iterate on features across frontend and backend with phased plans; manage model selection/context, and use AI to diagnose and fix build/runtime errors :::: ::::{tip} Outcome After this module, you will have: -- Configured a local development environment with AI tools and the AGENTS.md ruleset; created a reproducible prompt checklist/playbook with doc/API citations -- Either created a new extension from the template with at least one user-visible feature, or contributed a meaningful enhancement to an existing extension; shipped it by publishing to PyPI/GitHub or opening a PR with passing tests +- Implemented an imageโ€‘editing feature in the demo extension (or a meaningful enhancement to your own), verified it in JupyterLab, and committed your changes +- Practiced oneโ€‘shot vs structured prompting, model selection, and context management; comfortable reviewing, iterating on, and rolling back AIโ€‘generated edits - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: From 64496dcf0dd60f1c394d062105bb69a05c7c070e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 14:49:05 -0700 Subject: [PATCH 081/118] fix claude code installation link --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index e3393546..ca36b24e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -330,7 +330,7 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on 2. **Install Claude Code** ```bash - npm install --global @anthropic/claude-code + npm install --global @anthropic-ai/claude-code ``` 3. **Set up Claude subscription or Anthropic API key** From b324e43dc5cfa3f4ead17a53075963df1d8c0223 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 15:45:08 -0700 Subject: [PATCH 082/118] shorten the description of what to look at in AGENTS.md update cursor starting instructions --- 04-materials/05-developing-with-ai.md | 47 +++++++++++---------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index ca36b24e..cc1e89e0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -389,7 +389,9 @@ Without proper AI context that AGENTS.md provides, we observed AI generating gen 3. **Open your extension folder in Cursor** ```bash -cursor ~/Projects/jupytercon2025-extension-workshop +cd ~/Projects/jupytercon2025-extension-workshop +# OR `cd ~/Projects/jupytercon2025-developingextensions-demo` if using a fork of example repo +cursor . ``` 4. **Take a moment to get familiar with the interface** @@ -406,39 +408,28 @@ cursor ~/Projects/jupytercon2025-extension-workshop Open `AGENTS.md` Key sections you'll find: - **Code Quality Rules:** - - Use structured logging (not `console.log()`) - - Define explicit TypeScript interfaces - - Avoid the `any` type - - Use typeguards over type casts - - **Naming Conventions:** - - Python: `snake_case` for functions, `PascalCase` for classes - - TypeScript: `camelCase` for variables, `PascalCase` for classes - - No mixing styles! - - **Project Structure Guidelines:** - - Frontend code in `src/` - - Backend Python in `/` - - Commands registered in `src/index.ts` - - Routes in `/routes.py` - **JupyterLab-Specific Patterns:** - How to register commands - When to use `ReactWidget` vs `Widget` - - REST API best practices with `ServerConnection` - - State persistence with `IStateDB` + - REST with `ServerConnection`, state with `IStateDB` + + **Development Workflow (must-know):** + - Run `jlpm build` for TS changes; restart Jupyter for Python changes + - Debug via browser console and server logs + + **Code Quality Essentials:** + - Prefer user notifications (`Notification.*`, `showErrorMessage`); avoid leaving `console.log()` in committed code + - Define interfaces; avoid `any`; use type guards - **Development Workflow:** - - When to run `jlpm build` (TypeScript changes) - - When to restart Jupyter (Python changes) - - How to debug (browser console, terminal logs) + **Project Structure:** + - Frontend in `src/`; backend Python in `/` + - Commands in `src/index.ts`; routes in `/routes.py` **Common Pitfalls to Avoid:** - - โŒ Don't use `document.getElementById()` (use JupyterLab APIs) - - โŒ Don't hardcode URLs (use `ServerConnection.makeSettings()`) - - โŒ Don't forget `dispose()` methods (prevents memory leaks) - - โŒ Don't mix `npm` and `jlpm` (use `jlpm` only) + - โŒ No `document.getElementById()` โ€” use JupyterLab APIs + - โŒ Donโ€™t hardcode URLs โ€” use `ServerConnection.makeSettings()` + - โŒ Donโ€™t forget `dispose()` methods + - โŒ Donโ€™t mix `npm` and `jlpm` **Why this matters:** These rules teach AI the JupyterLab patterns **before** it writes any code. Without them, AI might use generic React patterns or wrong APIs. With them, AI generates code that follows JupyterLab conventions from the start. From c3166bac24d8c01931fd1b2c3130f47e6ba475a5 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 15:58:26 -0700 Subject: [PATCH 083/118] Clarify instructions for using environment managers in AI setup documentation --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index cc1e89e0..fa7b054b 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -463,7 +463,7 @@ Open your `AGENTS.md` file and find the "Environment Activation (CRITICAL)" sect **All `jlpm`, `pip`, and `jupyter` commands MUST run within the activated environment.** ``` -This tells the AI assistant to use `micromamba` with the `jupytercon2025` environment that we're using in this workshop, making it easier for the AI to run build commands correctly. +This tells the AI assistant to use `micromamba` with the `jupytercon2025` environment that we're using in this workshop, making it easier for the AI to run build commands correctly. If you use other environment manager, adjust accordingly. ### Verify that Cursor recognizes the rules From c65eb49f3625ef08b10fa04e3437beb3aba3b1c8 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 16:17:53 -0700 Subject: [PATCH 084/118] Enhance AI setup instructions by detailing environment checks and extension building process; add visual aid for development preparation. --- 04-materials/05-developing-with-ai.md | 14 ++++++++++---- assets/images/prepare-for-dev.png | Bin 0 -> 362435 bytes 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 assets/images/prepare-for-dev.png diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index fa7b054b..87981671 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -545,10 +545,16 @@ You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM 5. Build my extension ``` - AI should respond with: - - Checking your enviro - - Mentions rules from AGENTS.md (strict mode, camelCase, etc.) - - Shows awareness of JupyterLab patterns + AI should respond with (Claude Sonnet 4.5): + - Checking your environment and switching to `jupytercon2025` for the rest of the commands + - Verify tools like `jlpm` are available in the environment` + - Checking that extension is currently installed by looking into the outputs of `jupyter labextension list` and `jupyter server extension list` + - Building the extension for you + - Providing a summary of operations and suggestions on how to get it running + + :::{dropdown} Here's how it looks + ![Prepare for development in Cursor/AI dev environment](../assets/images/prepare-for-dev.png) + ::: :::{tip} You're Ready! Once AI can reference your project structure, coding rules, and build your project, you're set up for success. This configuration is what separates "AI that generates random code" from "AI that writes code that matches your project's patterns." diff --git a/assets/images/prepare-for-dev.png b/assets/images/prepare-for-dev.png new file mode 100644 index 0000000000000000000000000000000000000000..05c76ef8708c08b48a9a64d4691baca0885c9bd5 GIT binary patch literal 362435 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 z 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

iBn~X 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%0A_EisAm_)D{Roj6BM_kv%Y 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 Wg?il5*b0K! znosL{BKgy75(RKu-JGmX^orwbU9=Wi(KEOa!rbB57_Jl8WKn)e{K2pJ9wgG024K9M4U zs^Lv3tBnbm?x&4ak7)bW+=A-;W14J|0-hpYO%`!=dH%5cMj5SRauu(tZScdfx&H2> zo+UdKL~rP@3>a{&HG%sMInkQp;o5!sLzca2@X6=ORIF=<@7^5c!XsW_C6WM()`*^<=OXo)p-F-*d1{nx+-*ak_3ug}&_zL7 zl~K|Go0NOGyHyc7?H&T8f*#tnp^)l*dofJ^ExpB61K)9uO(a4jfD6$u$8^ZcB=!a; zJcTc{cPHluH86qs7LIFF!SDZs7E>b?lkY_Dz$|A`x}qzA5}yD}5vq z{5#|*j7sRJf@a0zj63ddHq?xMkTP}u)t!BR%Uh}V+=!(svFtOjhKGBd*@VtyDBUDQ z-yhOY=M8B)1Kuw!b@fhj+x1lOKo!T9PM9@aeW3SI&bqlZ9h4qq>JTwVbx1vZy-nzY zOZ;Jh%wE#SQA6OPqZY}8gaYN5=5L7A&7<5ZblQC~#k`axDk-%1^~C5lbb_rd>gy>! zMb$=$k)S1+t|XUJYE7r4){p>nf`}fowHVFPr|o}?(8+6&K8enka%nnISolVL4aiho z%)+krrzjd`mc4RE3jTz0wQ?hEpwx$-mAK#_mjh-79ujp^M;Rws*Jjka8s|p$J$&GA zWQ$sn9OyK?K0oA~qU~!IvC5J33u4wz$%wb>7TZp~Soypi>@gB`bSP&Lv$m)l9e#t< z;x2kf`N(~Wmhn-FEjS+PT#e<=Z1)Ur0|yTqL0ILCCHeEaEIN`wiV5^%U?7FFm875K zyN$-G9%G5-odB^-{gOX3K3@Cx462C-$ks3n3wL?rH7odG*I}-03y^$Dl}B5%=#kqU z!WVuj6N4H^?!P68`MfBNpx)9%6dx-WQF4vNS|*9a(>`haYKend7sRkdr>#=sXSG)# zC-ikSH=Y0&QBxW35Kl4=5)8UDrYX0S;`+MwtVoP)MV--DT<=8J{;nZD#>uH|?#Ux| zdy8<2uzZ0OF|eZ$E6sC8&|+_29Cv*$!_Ldb{=)9dbMfVt5LgsVj{7JN8yluvR8G3V z6`?|*b_Pp1Z)fkBdszWQleSgG@a=iA>DvF@u~)z*FN@=3fgkGG$x14k?(*s(+t*2k z?up+alx*B(>R?dC97JjJ*GQK&$=j$w?UKQinhZ7EOg-?YV~%V zVdjB-PLmnwRRjjVB>CbEn{ikwy(kqW)Sd#$yYlG81N#<0NUf>$ylL>m86DzuK$yVv zu%M>6n5LsC%)O||gT~gt(Y`cqJ)*J1eKksUxhdQkaZI~JSsd}x4ffh?hdCr;1^$s| zg-YgDj~G`KmteC+n~iHEN6P=Eq3xLO>iuG{r4XX-v@-7M1zMSQ)U9TeW%!zGT7W}D zGby6cRcD$f@=6#y(!iHQ_}Ms?cOO{j7vM?c4|{oMA?d(4uBDZ=I-mGGsUk{s7Ff|~ z|Jb)1o%_sJSAKOkT+8Nl8iRZkl*e603yk}||K|#J^rdx{+NNXkd)k@7!R-0I<0@3E zg7$sw;2X@VfjsK1<+!}X5WUE$+=Z~%*MT2FY;dPjjeVab>R(5ur1WCvBQKYxCb;gt zN(c(_|9)c3C$EVdT%IDs>t7+^xXhmnYfL<><1kKjF=)<9`k zfd{W(Q!Z4et{GpQ{Ak`Zh^{|EQcwE0bv-pN@4$5;-J1y{6j17kd-jYjA8K}4={nr% z$=C#*ZMTmyR`Aki2dMxdqQgmO#xCRfCF`$#QZ|K~qSXn?7U{Ig4r}*3TxmA9`&14>6m45wl z(qM<=16<|MXni4^30*@5m(DZF9Umd&csby~R2Le6JL~>!zy}_BC}q1SBqsw z{A?q4EcOFS4GD1qP{diF8mK~B5lwnV3kJS*`H#*U_kwR^->gw)P+IIE5K7GAfY_3o zFag$%>6`C)vC^?Q|Lj&g@{}K4!fPj4XTu>FIqFKVtIu_wIh}hUQ1AuG%^B!qaIu<^ z5a2%+AN4EuhoY_jXPvtwMIpZZ;sDp57VyWe(JR!du0n0tM8u!;bgE9H`L;r%-k(Kb zpuy9$kmLGA1Xr!PpylhScIfb9YpMrx>Ch~V{`$AnE^~HRAg(#BeLyPB3==+^bHMs! zXs27xZ1R3{hni|@z_;~LgdsCJo&gY+>YB`X zRT=h`_oTa%r+y)M*eZQ9K!5J<+ha1TC0N_;mo#V-dzc>NLb{9K7XP*KAo`{go=4fz zUyV0nHVtUPsfp>I4K-v_Ca|{TbiYsHOW3qyw&&2L@;Dj_ZM46Pjoik;QA*y1no<%^ zg*Tf4kxm{b_sA5Q+~3 zNLTS}spv$g_0qQ;n$I1~0nD_MX-J#GA8S)f8UQ#FLJt4mC44pI;syr`-ud zR#41J9;;f%xeHryJij?jmv1k$I8kWsumP@?qjmfTV!Qm>Mtj=H4?q9&Nf&{v52rN- z{g~J^O~90t4(Hs&BW~a`>X_}d0Nbryd+Xb=c?7^F|xzmf$n0Qa|`6 z!kr`E<*wS^f)Y!9v`1u4P%v32&?QW(Nf<7EQK00>@&$ab-oyv3Xmy$kt?TW`mP<== z<;}U#nJYSJUf-9KrGjRg5Q~j2$(|!QK_*y-JD?7#)ZlVJD3X_chg=yUKI#*AShaM+Id84@MooIfLowy7;3!wa1ydO zdAv<7;=C&1E^>X)UNnf)pH%B}hCPXCDO)=z+F<&H+IXtKn-@6;So$nHG~U&*+YjVB z4IKL5BMv}{qJaWVSrGBXD7**S8rfkc)@J>{%>P<`|0Dr6!4rSoe|lA-7?vC|r)eG_ zTsMN#kB?xx+dH2&@ln=nquC2~65n9XPU#-ZPnsVC=1maMLV7+I8!uju-ZI41V5cuR zDY2B#5TSMa9EmHl(bel_1q1CcHY8f7E?M6)S~oRu)8K24X3OFV8Hz1*T@rU3Umd5V zY-9u5PkkelS~}9s%?5LPyTUkex*OYAYI8 z*dI1>u~q22wvFMvC9V5M&n!*wN@s0*bjQWr?uT1EZ!{QZSPzGU&fh#DfTm58RB?{L zRjT(?x=drj*1oYs`b|n zg_FcZ@qe-#%QglJF|z18eLqK(AGq z0!|fJT@4dVg!6`JyRQSEUAlvs=QLeqva1X_$bb?Vs0C@CwBn|^#xC&_S=Zh2|G`TO zo|WE$ybdGwb;a{rDi*(5(SFJ~9T1)$V;jY*VR_|TYFWJqIn~Q<*;^T^aY)liaVdN5 ztAkx(<>mMh+WytfVP!X80iBHDkN#*CS~zcFSuMdg8B8rbG+#mC%cF%Zp2U_~%g}#t ztc3qDTCW(=Qk*G#`hfa6Edy`Q-A7{j_d zG(sy=^5qfScYTwSVn>pgE*~lZPye(_`}N%Ou#>p^TuxX%TVm0(0O}K|l(mnA--GW< zq>#hV^zAEphN5sET)h<)RdKm~G%zJ^WCH+LpFUU+K2UDc1!2wU>|3iRT*qjjC(`~( z(hyd0ZSR;>bGWc!RI;8Gen4%>R5Uu}f!RkJ@)bLzb;Xx&L8SNig{60O@fF^|hrka) z{&t+jPdQTuE2$^sD5P&cVc~z&VqCx@p+j$n9TG_@bQ6rA>C=;NCWqCFR!-pmnRY(e z80KWeLEvj?2+KeUfq1B_a~Dj>PHpy`mkc1MWjw_^`ZfQ7=iYAoJ00K4BcFGYnET)* zEiEgSH#rtUb<33SHmuNik00X>k~9 zjXQV?&xIeJlLn5!XusgH9=BnE6*M=`JkUV6@LwO}RZ=;xHv;Ly1EqM60XoPEI$Lep z+OK{^)9pT? zgKg1CaRx|?jUG-_*NUWH78-$Re|_h%GX#DBbuYqw^yG|4R(2 z_1r4R<4|bK^?WHO?xQUC@f`;m9nHTyL{flaN%cZk_r}A0@GlSM|9SiW+`7Lj=l}m@ zgC_e0;5i!t(F;qh7}RW$I&yEs(FK!!0i1O*z|hff0615jorhVJ(1HK|e?Ht^e6yhi zq4pQWo|3$x0X7K8=btTec2f{IPn*PPl>weJ8VaxTrTNluX;yTz9l)T+0Sg*&dWe>G zy~@-c&uMJ*e!=5-;9%F*yODg%!hkSV&%PIMD>JEWIRJLa0KiyQd(_C?e0{U;jl)`6 zZ^X6*_P&rm2mT3pd3v>Y=cNT)X=NYy8<~>rx=3T3q?@e(0R9r-HZ6G}aE-c(oKfiLe1*nefb0MnO2JddSD`pW6-;w`U2s&GSp z!$?ETrAh+eh2n!B^|9(3lw}f50e0Xvz}`dQk3&sG1o1empAIGEZUbCeg!leuKVa(( z5Mld`dZLOT*sfMzgfAKaYjb-+UaF4yRod-Io+02Zj`$6PUy$RHaKJV)C1*K`gK+sL zbSz8i-Cs=uKxx=O>9UB+gtnRbmP0Q?oNu(EV8K!2JjC2A@U?06E|6Ez@8w@q3S(gd z-hOxn4E*HJM9=^9!RR2Om%|bM?azdYf1F^5olE3l8N{@qr2nth16oq&ZTje=j0)p! z|CMkdj+5<4;M9I7;dlYViH|wqkI>?UV!Bia=iKV~N<_rsjo2FkI8C|Bq9@-Dz@Go6 z2vsTvn5W%_z=lVexLr~QAUKvD*aLj z22R zc^6z)4wi7d`JYk=*BAcqK5wT+qmwEs38Wd@;u4~70Zb0CcYnSe16AGs^`PoxL_Xl& zIo0Q2zWzJG?%begE0zI-w-cx6p32DebD!+PQp;4udQ2@~$7|7g`R;P{sw1~mcIyX3xjWpI6a)DI2-fC6k@ z5cl00R;P@oeSMYEMA|Q&F=bi3i314R{`adXQ+U(>?`$#t;wsSe;q4O#Z)O13{jyhN zH()Po1M*@j{}*@P{ZIA({vRnv)~g?rFy)pxbNi^+Y92+r zo<&mP5NOuGcr$1_bng-&>R#IO9?`mw2dO5htcnp~MQ^NL;%U?aYeuEih8qFbCW4v1 zuen|c64O$b!4>o_X& zyjaC+zj~@ni;qV>4cN}QPekOPwu9oK`&R0{-~;CynTJLQ@7Cka)IxT;-``X!3MnTqv@MO)?Uil#k$Sx?Ue>AqrBylYPsmY!h`aPHQ?bo#Mtn>w zgx_bLKsNXaTh)h@!=!h|Rb24k2=UD{E~RqyUN^Dr=AireG??H7db5%OuLaH-UA$^s(wVG`OT=>yF(D2JS=0m`Ok#stLFsh3&Z0Ms zA+l%)2L^fCb&x>oV%GW@oT@S#t$5}D5qwUju+(s@;SamuPiJeGtGI?xcee)JZK;nltN`+@6hMD*p7gzb`uEFY5u;hKuNYNyIi9F6L%E z^#KQ{sZIKHGx->*AhnyLuGN2FTm$zNO>y}nA^eMmfq9(PyX7aHD`6>C(SUQ=hO&?G`L$=dnmjX^$BcIoH+D`` zqjS_vo)sHCa0L$83=%tlAra7~i}(-wBDAy_MB|$;uzpU^7-K|L);GHZDF80OfP#JRWeqU87Wm| z<7t4{qrk#YVM)uc7w*c}!01qNr#ZYDxhg!6MqIxOSZbor2?!7Cp+yL@_|Hl+LzJ!( z{`aga>Ye7=f^>-H%k#awg6<`$>lCXSqnkLWl=HbOo$@)-3VBWB`$P%0M)PVh@(#lO z{3SdVXq68Q;Np==+6~_(fCnc&2JcgwgE^)%T@$rrAaEiz9BGn<@kH4}@%17<0G9FS zX5`)}P{(L_yN(%%+^v{&H8@xluD8BNs7gFV0uR>JU#l0E=lKzC8RwuJuizgQ-Ix8h z(;qCO&fa&+|AE*XrN7EB^wZTWrYo}SuHgJf+$+X|)m*DPUf^jX5n>_rk4b+fXQBAWpz`*4hM zux{y{0BZ;G010C6e{R69=Ns@>r_m{SEtE*}+lAH~;r2?WIn!%7z-2=Nf2Q#hb3KG3 zSoE4)ATiD7c{74LtPsBD)=*-16?&k`QslE2DFzk)+E#^}I>Jz0G>&N{yxMFvW)>-F zH*oOfyhBm|UddodQ#{ql`_8iMRog z4Ti?rwhGR$p&V7XFr*mv^JX|#6~S6fNMR{>u8SQmj=`a3^0Fls?!uf9J%%kQ4IZ&^ z0l&9JfLMZLv{*{>d}^MU5ShNP`bw#Et+=!GF6QMe$^ht78gh%aku9Z{9Tdv#xE*eR z%mKWw-r6d7TPI+k%iO>>;`pZHAI1J2`wy2$GD#*IBI{noE73{h$4sndyL!wn*J)Q7 zvd^J(35GKHHl`Z>3`*VAKUSVh7O=4czs_q$9ZM`wV(vx$zynPzoX550hpLqU#ypfNn->0);U2?42!!k85h0WGAoK~Q8F5u zw$ZnN3m1FB^>lDEvgcPNiC>gnUT~7UU0UNye4QFcrG(Z98}3dqdXT2GopOG2g69!h^A9sQ*1P z;g_cNRs%0XQ{rCG))V-XgTacr-TV#?3w9P>o_O%ZxEf-tLqM~M^5FdOOo#b%Ne^cT znxSQbkhE&BFSW0o&9h5~;3VrkkYBs?I_ovEm8$tI(9x*1VY?wA0W}J?9wg}`2V2Ng zB%%9RAa&tFyn%Kzzpq!V%=9Ig1uH9cIWqjPih_umhnlC{xtuxn6+VV8Z1ll3#jhhN z*uja)(W)Nu0EpH4-A5gk%Ltp+q2cosC7;3r4hU$KQ^$|jx8#R^RW>atGGQUoEsa@| zQ4U#%NKxdQDa=vt72yIdG~V! zK5t7->=Aq!1g{}}!JHvWzW2munq%N${9q1PFu{@P=t4TN&>UDRZ{F9gk&NI`jqBkH zsk=#+cUr}RUbF(`1XJy;crNhV?TIhGf%ry`>*WDoGfnZqeU&rd=3BP8)NKO-oTl!R z%X~F;=ZHDA%A4?7kv=te@U3wk;4$eM9Vv2Mm$2-rVRiHU%&C7GgSc~J&|s2yl@HNH{VCw1zonv$^-|()^9`630oVo3g)|Y{K=-O; z_c$}NlkN<3MAM94FC9?RT0p{YrjC6T>PRu?T8B_X2FWz|_$Gc0UGJ{;7f~YeENK|N z+nmN?o%eZ#z2rI&$tW&P?AZfBbFoar)P4Ujo;5U`LrruMoA1LkKqRFrrZ+|@COU26 z`cVa(?p4&cFptHbIHVo_L8{h@y|UJ_@3HyQ;Y)Nv-CH3QAslvkOh2MR=7V@^3%7sYcU6Gm%X1x~dA{#Yv9zZvVQ3mKXa>4PUeSjN-o2qGHU!3$= zH=R;$0n~FBBnJ|a#)ooO1YO!3g}d~Nxd*6kJ=y7j-PS%o zUlxd`LuW~;VR{`kGjVZLGRYBz1pIa-Ahc?enS|K%O70udn=XzVc@1EZ{?L{4M1qzo zvbR{%@GHgnW=AGZ%Q8A+lD@v0?zhMXLidkdhCU_rQ;P+Tgoeag=iU5aysI3@Yk^?x zE7ubn3|mOO1tF8h|JK2^OAewybpeWSsPt`aZ4O8@{yXw_KS3v?Ij5N;wiG+lv9|4t zPQyf4UR?VWqys~Tg-8POb#xY>#~eZ-n5CmZ4fU3{i}V^bzo6?i!7W1M0dIk7;@_;1 zE;R&%pLluB2eCNUvxw6!1(OPySLSEVb*FgpJb6MO8_&phIWl0d*uOOkoiln_9fmq% zpuF~53xFl*f|fGWbd-SQHBaP80|mar&3c|Sd?(K?PXFRp!IF3TqY{KsA3kVU9qsB( zX?*Ke7f|!?e{_^HSF%-T4m>2TO*CmHYb0Q@6XU7qo02+pB^Gru;SZNRg0j>*3vtK8 ze3(x^@V(Hr^oLHJgdvaFl*AB15Isb!F~e{YfY6PJzBT9e5M>RLVuex|dWOjYXr0cj z9~Z-tzcjv`=sKDd)=fSjVx*#5w!T)P<=+RCuqYFeyk#}Ekd^C@52ZT>f6uYJ%_d*g zga>C(HIs}Pza+l0*_4YrYrb0VxpK<;HKarh1H%*ZlgC z`!HbE?KV7k7f`Z@n>jju927IMT9D&XV3aZcGZhCChds{YYX&sR;5`w{giDr2dp8ew zUdu9=*3cO%BC@S`b_g+pm|)a?gWpB(tq!+w!r4~~lQ!_+u`Fom7EuPSI*R}gWabc6 zm}KcFlgPKK0uSb=`GIPGS*g{z;V=GcIg|6=BRGU_$n!{(yyuMHy8r#gP=QaG4WGS# zhMpXXC`bRPG<0%9tvJIVADBvM;$KhA$Jv)IF(NF0$qx88lT8^wbdAB*;=(qAu|XR< zhCmpIH-%NpvP+kNxA8|AXP9e9Bu#UQ4<4l$h+fucXDJRZ4Ewa^f(Si(76xy-THrrO zjR*DWxpZhN1lIb82aH?$(^vEFd8x)wkv+BDdiVPs_!FRH$5Qsszvs4nJ&FAF+9B0N;AA&l75m`ELvm- z+35^3V>>R~3SmzRM#n-_?9*gd`m(h*T2ofMv7Sce2UpDpdV-_TQDV|pxpuuw84PQe z{n@+DWq? zVPxNi8jgsev1>KW_55#)PLEjJG_OB43>TZ9S&|`=Y zT}*6%3fT&CwY?1G&mj~DPvUFdZKZQ4P5*WE@23*#$Ef^P84s3u_e}&4p5rB=6XL2n z$=+6MI|U6+!&B{)6MQTPWSxEZBp4F!r&J2h#|<5-Y?n+q8*j?qq>~>jw>;=Q&zuBu z%ioaj2Vzq$29tAAWNYV{$``srlVLsLX%_T6B9;U}>v`MvIrFd9_FI(%Tbqy5yu)7) zn0a~a<;PT)+bP0Mi65EPlSU2Kj99(MkXF*c58X*9L*Ol&^uR~E>WZyDJN+cHX}+JS z;?eQgEd0(p=0HW3k`G`1nsYwCHa21F1EE-^u`PjY_+rlbKkxsqr|`A{8pS+l5K+3P z@%J0uRc)`|&$wXca2qv4)4rTke1MH;dSjNE5etN;a^}Rh!XUX_+Nhi%jf(M3ILbl5uYA^{FlG}Weve7h5qX(0cM48Z78e$ zhfnbPSGbb>@5lYeEP2r25Xjc9y|8urhiCQsb3iKO?>Uk`Qdm3uxa0vIV>l!KWj=47Ia;l?mfqeNS-ft5jzm7gt5kL+w3IEHajPxyU^-3%P0q#+; zy{CEOABn?1Hj6xNDhK3tQ8T$yVq!CMnbg0Z?#>BrtMI5FGbjD~cLvLhkb`f1f^Gp#Ny!#_QYXqh zbNm1OnPl+BnKZ| ziV>GxA^+>h{P|_z zw-t8u!L(MV0N~&_Z=Hauu&g&ItI0kcwd^`-%F1=1&8iW*@dnw=RAnbjGJ4ujcpSCR zRI5Hk_qld|{JRJJ?rA|#Ts$Jj=EM`*P26_8o)}@a%4Ah(*n#7sUZ07sh%z*Q6&?~0?Ti}{aLQpN5_0PJ-#z<5N?fxa+$Z(4=$( zc@NjecQuQDPe_BT+}kz+*<9|5CzZ=>>B0EZ%_Rk-ONjKd2hIZp(oC1#AWAvw&huEb ztwWCr)qeX_w$IPf!kI#1bc(G0~xtZaRdE9Bu4gEvCO^M`po!BT{DMW5^aQf3@6y4->~k8>sErgIF8A`fVE zo}(x;Wg`e%1Iu%25$U<;@dsH_i47Td5chM|uX>LG&FoC-*ep3{6^LI4S~e>P6hSM| zE5~1}P#17pxU1L53Dfz_?#}{B;GjTeITeXkfMvaSFt`Vl# z72nhKSoy#yn=?dca*nwLzq`ewTov;Wdyf8SuYjrnLJD>@JKNpMB=po)p%C+dY;^}) zy>N>H6&^1+?(1sT+{8wFiQ6-+-<(W4w{i;Y|pLF9H4} zX71eMuIvlMAO+)mF|7vnWm*7vN3Qz|s!N|Z&j?SuUxGoBy5ESFuxv!Brxlj&W47@I zM~r+Wzk`f#`4=hiqznFY3UuAoBmirdoJq1wvo;`h1=@|vaP9I7VpEb21q}g~hz4$^ zhMOA?C<5mNJ#MR3#031L3pPb!AGPv=I z=jUciVbdc{6oI-5L0*mWF)0hOH04YYXN0VaDu2%wG^I6AN-UvtVA$mPFC%IlSUfKRMd>jpYtnF4znkKIM)wcJHbIc&3lb4)vGS&()ve zcLSJm^NR295EWNFQ!hqAtEneUa%Kn*S|onPiM%| zt=dh1)t-w|>S(!0qFx!;Xy;CL)Kqv6rJmJVcrO5j;pTQ;ENt1KzIsyIc$qE$h&(sX z3r{+0BnsK0hJ`MOR)?FN<~;pS?RlpH6rI((2DTTXac*W}7XHyEiT6C(VeF z*V5gYvWiclq%^IGvrF zX~lm^v@hwMZVEBS;VvX<3^J~xMqFIX*=%Qxl<&nE93B>QBJy>HOtn(GcO(LOIuaE|0pE zeGAbJg4wh z?l1GN-A|@Hx_z29K~A7uQu@njBNMk_g-ArHc#r7k$H-ao&GMTuW<8sW$y%i zxgy9$n0g4trIB2~H{3iJy_39yIp-kgGTP&7yZ?0J+D2VCvfq9D+kC$j@neKR7A^&S zrqUcYyU7ZfME;_ZtB=4x!xwK(y`zH=;wrb2PSPTJN z!x1;NXRfAb`6DUEA%KjN+di~u&21?lHS(97`*VeDIaU>D^5$OH##RbXkck`>QR1)a z+#ZPUmrb6yt-~f2BVXyQHqc^KOQqBOtcR@?Hj+3+```EE->o;W ztnukM%Q7Clmy7Ci$1+R!FFOywKP}uY9zl2pf*$Y36|$mJP_n0hMphY5GG~!uA0Jcf z!Izqz_afg}yY}OJg{*M~*Hq)~s;cu_jeyZ7n0r#h3=PUWwSzo12jZ!>X5nLYwo*;C&6z!zFVE93sLL}6h-Fo;mm3`4>w6{^qUX}H?O5cMd|0ac$T_N@ za`x5tyu8T|N7KTipmDufP>MN#M}0Z`K1x!R3*$66C|6elMR|e1pkn(-Gh^JVxYt3I z8OpH`*lC^Sk-YV`NG7J2-A68OQVN@(RR{K_q3Q`9IM1G%6UYjA4LPjVvjR(LG9M!I znpPIi9@91QEq>^i{0r+3PT^(;RI;k^=8w-r)J2RchaFRA4Da1u)n)x?2Zn3i|7j}t z9^`e-9euXL1EJ;Z1zP_H&B!#D{Do28u&Xfs&PuhupMOR%`LI4#yo>^0=cB^@;%!DB z6qgipn4VkOM*mZIJ5*Y7cXyelbnlZK8kJiBrDEN&+g}(!=eaGaPOe^p)dcBE*&Tl1 z3Khv6>m0BMDAP%z64_Vut>b>9;8}7pgZ!h^zwb5l+S!(@k8A3<w$G)bi&+qU)lLmiNkszPIos@g(QUadJ!`#CKy>K_{*~m_rg&)8-|yT+@ZTDPmMbD zlKgckJ`6%bJuJds7)#SYXM?+ztcOod#=I+)v8{yv)j%vXDef*PU3jE?llL1AO53Oz zCg$qcrs0Qe?z0|tKT-?o2s_D~3sjnqTms&ho@mzl4>1a!4Ic>|<_`n-+B&#+d_u5^ zG4cE>$3?ynX6Fl=+;TVhlvg{R{ZRJIG&Gy|tj0WxBR5>A-xj$mzFw-av-6cAVk3+C zj?LA3W(GT{!{=zn?AKNgdC`uS1mC-%gfpx8(dA2*u8*G=>7=iA&vh1K-|#?QwWiPc zjW+RpsAH>Qe%pwqn<%cQp3qCly#0_WfNf8l(t0DW_&HM~N56@C=}_`BjC^Qf)*@QH{MwZ7#F&3g+)JjV~OO(X?fo#+~omV2(&6jmjM@(1HNXWruNeFeR1 zcY2i<_7lF7snt3nmRbR&A*fv9xKwwiJKL+yjAO3_clO(&kx1EGUxFgVJ0g4O8x2Dt%o0zwtmDqPJhL3UO2WLmizb` z$S~Zsq}iUJ+=a>>d|4K!YCB03GSe5Co{tP%DamTfQ}`^#|Hfn4nsP=Nfar$D;;BL@ zjf;Sc6!G+sh_7(0+J^YUEyR77`&b6&)%=RqnHzQ6kDgetn=*c*;b6^jpLv;ay~|j$ z^LQg=kRn)(Le|d(QSu{<+7u`6R*)JAqqff|nJKZavvV?EGn-gDy}FobFpm%QQ6+CfxzH83nhn_ z*pDB9D0e@P z#|f_)jfFpyFI>ubt!74ViP24_A$R$UPF7%T{jM+Hq~6pxk6q2;{h4z7bONJ;yG3J_ zvnbKC8s?oCLLYMO>RgZZuLaW2?~l4Sw>qt!_}2NlZ|)+|-#uP4&j)52J>JT7JoW9# zedpBAuJ&W&YVv*|7yRQD)fpzl@eDi#|0zlk`rJRl79o+|bm=-lty>fpZ|+9n42rIG za2NOZR}ZtCMzL7-y`s%*DjsSD|7_XYB}pSn=$##FFEZ2U#QlfEDt11V&{R6H<6-zG zt*5N3HCE9J_Oo~6J>;DFxAF(xc=R*VH$eD4t(}H5h?n#4+_1HAT6JzBB>OP5h*W(e z!Y6#0aKU5qroFIFrABDoI_-oj^isE3IMCy~dx5K0Spt&wwL7-?w`JuxgV6d6 z$Jv)2Sn7?VysCDpsm6+HY^cdm(3eBAaf_=3_$#|jp#tU|J4F%w(<6o@Um`X_4tEjF z^VGB6i;*{0yfH)twROiOIX-x=iDj4{VEs3v)O@^!k2}C4RN%Ll zn3<*??xy|}d>Sz{iIPN;r`Nlv(b_DA}z1K=mIaIl!wjNaT{i}nC;UyH~(bbA0$&Ut^X zH%R?-KHVvmKoKunXmcsSXq=swHHj+3boTOD60@LZ&st&U=C;^Nsod_7af#zzm~>>Q z&-1ZcGfZ=5WcB;k;mgA5;&*tD?SFbcM@LN2ExGadFnt-nJW~;LD&&*ETzVl*mhjPL zo7?;18pIXzcFxSC$ znzTgnPBHoxR`u-`;t{6vrY$6j%C&e{!#g)N4DzY3f^LMo@6*!W;&GR%CTd24a`hRL1Q>dy)$=A9gAGJyTNjp+#b)q zqknWSsLM$LYDZT42+n(3m=e2|_8?0IOKdF{Iq*y+O0D3eJkt7C&D2U^ca5|DAC*fF zQb2Z+NQwOPDrHa2Ue`e_JAth4alf8ZGO{Lp z!~eeh!80^EJ>uZGAs77ShnSNTn40rxr|vhk=d}{v>?AyRV)Pcy`WuxEKU^-K7(74A z8r$kP>ROdB|FG{+QZk&~SsZYSlLs4)OjgXS_x5_rf`oodcu}i6&Ipy*Fg2zoX=A`% zCWNe7_L>@OZ`~3vjhYE|w>8nj7}u$tbI{GN>{r4GPboJoltTHONV$WJFt#^)S2NQT zlc{8C*flxF5&FJ9ouqw_K!*Q^VdbkoA$}MrBeH-g!20`7ZyLU@j~KAXBW9yef1nO;T#?p3$hl zTJe~^6ItWCsZ@cA&Py2**rX!XHWU-%MyBovMemtYrPpkY%rGZcnW@PB$LDifd6g2L zPpd2shJ)%EMl4munRF_B*NHez1|n7#EA%Z9j-3n<46FB3_v3PweK)@vH8X)a$A*|M z4W1)U*xL0HU1^`aUf|wKP53UqtusjRU@iO^4Xh~vE3S@o$r-xUG11Di|4y#@K;Z9 zvehfmxnYOy`ZE%)_>tMWokm@$gUxZf_M9C!YvLrWBi^sK)w5qzAuN zm|?r`Ia^(B}Cn?p6t6jopade?*}9+BQz!c7S+p#h)w#edV_! z5Lu=P>$#2Yi;E!y>5?nQyfe|azFb#@CJW827ov4~beSi=v@ua*r}w$$IDJJ}RFWmWzAE@7VOqf2RA+#tO7K1& z!4Jwd@~E;o!I@xy))gL^I3?j_a0q^p4kkuN(T9;xzq^nohBw@j=#3I?t6?Zl6n18F z&z%TEVwwGGvWj@yJwL>0ze3a55J=Vu#Oz)cFNpBbjl)VTe5fF?6f0%p@viz#O7)4V zRAjPz&0!mHi~nlR(z^Jjo*bs;V;l*SwE}bt$FwBiwFidDhl%#^r=@NOT?cs4ihs2OP>206&0ZchGJ`OzS${=vC}#}o`?peXFRpfj=9f0#0;k4hbD_KQv;|R>g{J;&bvg|RJrWn z7iG?`H8{7}YB2`eg6c=-)hE1mqlE_py^dz91L}lNL)%|pCfu`L@v>MCrDEu49@#`v zo&2nY*{mDr>tI7-hGqmaLOh-jm@V2r3MFfkqC5lH2n<1aJ)3^)u0-+rs`oFF=VGc} zm~uowp&-3mxuUaDH~ue+#~ma1f@+qHHl^!)YoUqC7`o<>Q09Ty&<`xZ`}AFOpIdfu zV7#`zs?ExQaz;M2*kSH1dHM+qse_pwhm1Ywy60DUPgkrK325!Ty^n@Ji)d-br$6Kg zA2G8o?uqeWNhzDtv?|_{!`z}9zcEWRg3Bd293H$5D3%E>d$o2goS56!+N(!epP5QAYpP}QxiBnb~O^HR2Fy_uz0{I=hNsBo_(H4tyXXpWi`{YL zpzlS^3S}tmpznjqgX#qNhp*m_E`2}VciapJo#RUyMfniQbYR_49Bj z9FSPA09u=_rtY}@a%qPmcZsTvZ9MUah+Kn}S;cDPmzi0wytg=dG;GV>o!#y*1<#W6 z0nmyjrePq&bdR^68Pp&Aumspbt5iQtn{Akx!qPXc)yl&NZk3RIGgCZaSBX!-#x7}-+gDGPlMR`PC5hj3(l1kuE@* z@+-Uap!Um*A=HS@BEe{ejIi=H(U*!hM}+UQT?+%}S|jm~(jsyCU^M0P(8^>?99u_0B69fR5-MiBeq9kwwSVx= z{#tgPr8trLJHJrkuqh3=)fbY;OkY=bMoPY{V9QSj5@sN~S@$SK!sVIBe5|_WHLRdb zw&VBHHXWbrBP&k8I}b5UhtLLJPYl5mvb)~(LG1H!(t^v2{1R;*#@7o2YuPUITui##OX9vcA`6z= z#p|Pr6R352qY{5{&rLR4&-f1rDW5Ap^I$AepVh`_lJZE(q*O&R%fr-FKOd&WcS2vvQ zZuN#nfBkgHCyP-N^W5IAd7G+l7@)qbuQpz1nWc*;p45RjrsfS7>DfIRcWK!5l*8>X z^W#Oawfy+@MM>L!5naTa%|ml<^}v0F&aQDq&R(po2iLv1+66nhi1bUf<>om{(YHtb zsDL@AO1)t&9p6$$`KI%#-xS$gTSb2vnKH2HCB?YJY^+GO1$+=k`6Nl6e$O?jpX(`4 zr-UyjS2L0-lQKdT3IuvL}X1>ve9v+2nIn zMvBT;_^@G-K((w9Prw(jA1*63ih z+m|@VwfS^qVyWKY6ydYI;CQeS6i7AY?&Qb6yy5LOW!A(^xBjT?XI(ay(08?cHQ=Hz zeS+^oS3$r%^`6k%Xn#<@41065>#J!WvwxA#W(2FVl%6%|n{tANGi<)GjhRO!*8x(-C~fw3u=}QfDxdRE&??w4GjV=}8M@lt>A2Ttrep4C9Q~Sl84h z$@y{V`JVLvOi25c%VoNi(wP|N3j8z$mDHczo<=h{ z@UC~RbVu@!H@({VvvAuw@e8gw@S0RcVX0a19P^f)$>%O^qa}{_AQJ8jQ9^ej$Q+Jp zr)MN<9`#a}@5^49g)yMDY11wbQYiwd+4 zy*JdfUSidgHg?ifU}mT_eP71J6Ls_12`|r^JPAFNQW$Tb)q2tpT~%y?QT{?~<8Js| zEUiC*EbR!n08@RaOHR8&&7B^~u;)Ytybh-l#S5`=U0f3JB1R`=>4M$yUtP!_M!QBj zF*JIHOb?cFaw+3ki1OV0sKbF<6yd>o{hRBTh(BqC`v1d25Pw>waL_X2*;XsP*p=IK zqe5zsmYkbv8(Ov1qlgw(ua_X9oNz_Y;P7OKGo_JZlFN+qAQi|2pBA)b%#bi~CwGxu zwyju5RDi1A-;_nB?^5n(@kYMlfr z^}moRDz@zGyc{*X`_eKB`JnV&nDzo(F){=0EU{@?RJA493oV4YZX9F`s4wN_HKLxa zX!er@k)}2Vws={JGQ5i^i(G_RSd-CHU&Aw#Z<2V()}3oNkx4HP>o75hxP;JOS#z=$ zJrz!U7IEXSua!)S=CfQ|O>Vd=Y7_O(KT`Gz9w1;7w(U<=>NycxOV%f>R zuPA-=mY4ex(g8*)HuGkqVIH|jtRKQg8K9q3vGu=^#C4R{%en0#!=}?O*XHUZy7I&RGnAV}h3ie6pu2#JWA)~Lxj4=@aB7}x6dLQ2 zIfdI%t3<c(H$ft9+Y;kQ=Lr%weNQHjRc6Jbei}waBiGbLM~uCWx<8u{VBP|= zea~h8N6~d3?fEE@52|@Af37&3c^in#y_*mixl{eX|3L{}S?xI=VaE&C$KvJ$omiDS z{tz99Qq9#++ZZbP$*n<4QCyXzo(GkJG9F(HW2@ruHgIL3pUj173bbkS|6tZH8~)G# zaBNMO)!Z~rnjmYNF0&Ru(@;$z#1frp@+!{uHVT(nPMRp)B*^PrS}DziwDkXHHT(ht zUZgvPUg*QS&rM7J-Cv!elPy)$2G%%WUTsp8L|q&t!oKQ(+nhTNP-$3_#a!yhsIwmT zClTWhdK|cz2lrz`6*&K$bY?trof&R5Bk_6szmRC#Z!p7eQiW^(^(+6pZfFAXEPYK# z!!n(JCwxUIgXAPPZT^3ulm*6sj7s2krA@)V505zk+&2`|hyC_&|CplxxgcZ*bTh&w zry;~2d-Tt-0jj7BH@N@Bs#Nwj%=?>p`oI6}ni^n}UF(_qRQq4(WQO4Wvu}cRrXUEs zf<_UC(ts|c3?@Ine+jqDy1hzHEok-N+=D`czRzfuP%iR;^8)snj3CBO)%I?xydFEt zh0E_kQB+1=eCLuD>q7yPm+9=Fok-9I7!9i7F?;-4zbUPMj$CGhY#H>-K4)=)PV!f+ zCqXswOsX4(;GBM^3yRO@+~UicTmi|e4=@WK>D2;8)XX`n{Jbx>kjcu2gor5~fSNx# zyy+y03rdBYuDyp}z0cmWTw5C|E_!pGYFeYit>*bObJXf&hg$J{tZHDL7?Yw}}b1k9o&e(YvX9AujBG3q| z$PA#*KJ~o2Chp6cgYy}5wK-v`yZiGKS%HWF<=OBP7P*PwGSGS45y zo!qOjuhwh#F@!fd)%>z$|G8Rk@yVBmL4}Q|`OtbKwuxL81J_hT@}Pv#r4yhm&`0E|oga~)_^ZYHxbgAQt|0z|Cp zZ!sZnYs#^%1Y3?)3T>N>&P6B0;``-n2)+dCAmw>d+E52gtYt}9S>_?*<+vw{v3dhX4#DFk1 z9Cs3+oPT1M#qdCCiMov2xS35&U-C3e$2$Q-4Jla%!P7N;$?;*_|%Nj?x*u(+6MNN zcP|bP%v6lEdi|1!ye3u)QD=L0Xn&9N#6Oyie)iw=(BS25lpAd?KvmUcg9KDeSyNuM~G*gMBP!tG)B1qBide$Nc5ie!*7aFm-;$;Qz~AP z%?Tah8(w~~+@W+`Z|py4}cd{A-v%;q77X4@&$QRV0O%o z^GPnpc9IDAAi?HEz9yn?9k|RB{7e)DcCVK4nY3lH!ek;;IL}63CiHT$8qrBG-n~XA+xr zjgWwdk53kBzuD2S5PJb?YK%uIhg2NZ)ms$b@cB^Q8?z5E&~}A!oTJ9R`+Ed)i49~t zH$w?qo=L<&vAp6w?&eO6gZ$#`?Fkol)-*EP0-n(VLXu*xC)h|6KmKDQjl<73l26d> z<@^@Xch4>Po{ZB-2a7!*Suoknimr%YvffNK8BIiL_2DQeH2@CeXX*g4)8&JpiYs#O zcu{NLf!&Q;U%p;)M%{FQ!O#IDk+!Yo#TzR|0I94ai|ETcj8x?|$S==2pvFxQhTEy$ z!$`jOY18nvn>7lLaDu#wiEvQG-P7%Sg&sw3GCYoxMjJMY?aRm5p1$_Lg;_D%!8}ZI z8kQtAsnhnBGb3A__adw2oL+P9W2{Q)npNpYfw@Cu2Y=hXZrfN8hnV6(SoJcT3=D-@J4) zxmYK)-E!s8iE7FIK#nDHC<-&XG-w)5Js0!|COcFD*mIoFrOTNSUH4JYhn*?`*HwQM zHS_cEzJK?g>57)cWx?@CADI157k zd!uT9K*7JgqxI|OToInrX$H9^P>#vxCsQ5F?0j>=JQl78tWQ}pzJwPSx9V_VysFUH zb*K`}wp^eWzK}8=v!v-|2IVkaZZ?bQjF&-|wX?b^;W1}+!@|Xl=$F$YWnDEMN^|>- z4rYw<$k~h5C(sXESi1Cd?|oC5YwXIF=~4)yCI(!D4I)f=b!GzBKDJUA32*eSV>_5% zuMc5yC!Pc^3!ed0-I2GFnWP@d_50Qj&2k2Jlh{$I=7W30epfTD3KU3C5ZW&%?_V!= zmqRqD(%gBbl5CGZSZ_CBM)yZb>bKienj#Yky2~~*(Jh+FwU1wVmGnx%naH#02eH;B zgLI8oFx}j32QjCqjJ=iZI?u?PsNp`o){fYu1h;PsnCH!;t6wKD_?LQ==MpF;@UanG z+j6nR5^lxiV}O#ziLR~*($dzC|KQ}Z)jzg%&SB&|ic6wmv!lUqL#W>GB1FnDo=xT< z51J<$HHZWrHkaG)3=qCTn!ZP_K0)39t2#K)bFt2{OfP;yv{6)^wyU}LD}z7=-vBLo ziHlbheRZ>W3t?bAy{-^(uVnp&M}obl9qS))$p1Mwey$YD<^EAf*;t$x`y>d7d_if= z+_VE@2I;AgU6&$~d>4e?lK7yMJ;qHs03{fH{&YzTQlDSB^x4|?c3I;Q@Yo;)0*h*y z-aWyv?rzM6^Pc_#*&lTp|9jzQLBOR66j=h@N*qTqVi{fq>{7aIJe*Py zSY6Kowk&JQcIx;WxXqMzPVYRj2w%HWu$4xQC&dPJy-jz=M)O=fN#*qoiNSH9$ChpH zmmT5q7c3mnUQsiGMrRXvU%AMdvpKg?-%hWALP;OCJJNPhUS4k(^2XL`D(;iI1}#g$asF3-UCPFBZzdQ)__1vm?eJH5VVviv{x-ZCu8 zc5NG0a*&uIl#&=4q(P-)=q?pdQc6TXN*V=*QW~VBy95d86r?4Fl14g|4(a!td%bHt z&sxj-`}u9(kMAdJGuL&_b)9h>`+gkzzJQg=-=`O_{1wWfjr&^l?JJb`ZHmcWP;ahV z%|?m=`a%yWH0!o-ut|TL1{7RdYaA+XS0I($6C1qoD1##r?=6JmcIQa3YMd3GyTz2R zqz@2lU7hzg_~7>SrAP_X4VPGlz!%nfQ;UR;oo7s|&i<%=jL*7?Mi6yWZ1msf_bU?g zMEq6hGIn09b8mA1pQi!FqMBf$^yG3~{N{$5fA}%(Zr~?pW%oFDC?(yS2@UqNW&QRl zOXu7RPNT3m>yx|t)(C0wjJ}?ovghZVT2-?W6AFFXc^?;tp;{j2yPcRi zh^VYRCq9p8|F3-euL=5$%h!$vqR%?fPA7R#m|v}s)#bIb;34g`3F4EZVonW$cU-dK zltlinQFt)t$wWhfZ7Q*+TcBg2XG>7Wk~s#aIYCHk#J^*}QZC9GW*K+|W5gYZK4euAeZGzA8q>t@Lw$o6yUV_ihT8J=D40l7 zw4wAgUj$VX#W6Rz`LqxE8dt1f)9o>I5Hj3!@YJ)yVBIVa{8ZNPLezv>l-A2ik>U*F zNkY=KuOfkOT8H}j;b*oBMzfA8<7<4TXYy0N?`_W>=l+^#P^&KZrxrlfA7A*GpJv=| znLJ{9>b`-hQ6j}A0X?2z`^NAzcPjI5U0)v@s48uT5iwP$yQ}1>*n^PFeqx(ooLcnN zi|JxTDeK-je7&kZKa44RiBcePcP)?ej;L&iSRZ4Ks^%OJ#b|#s(?m3K)4^A=;sNXl z$nm~_RMxg=(P~U0cef;vh=$7Qo|%7fhW~6E$027SM{zsU&ucMCrx(T&LcAP6sWSIW zbm#23drq4E+1h{tUF=i?q?^&^Rh4`M-U*0Xzu&US{`GB2`+rJo{%@L;-iTHA8I-1x zP(OZJ!LUqNWX;S?O(c{0$j9jNP{e0NDQeV|Sp!)T#i+ndk5NV>?VBMM!=hG5qMXDP z(?DHDZ0B~HRz)XIohz0Qaf6orr%fE+d1%A;0kMi7(sDtC?ph8#vFvyE)qBm|s50>~ zrQ&{E$lRt6FQ9BAmFhjE47&W_2O93vE-=;>H^ZXVhRS!;j)UfU?O)T z>9E+L9$Sj4AU57-gXww-VqrT3<)+^H7I4Uq#4nnB1n^*62xP`EZ1KyW>?C}F5}_p; zwr3(M`Ud*tF<0)lv78Arl%nXfs{To%{~CxKK!obMIE?>ux&7m;Tgydv2o7UrJ^#-j z?;i)gR6VF;d`_;v{5S2$GqC_Mvhy%`!1d?rKO+Z_&@YA}uYVO>{aezU{sF*cBWlpl zLgqh1PQMsHW3r+7`_lj1`TqgmIO_n&sBYa?>+`=|_Fs5Fx*^siH-rm-GZ`9b5~iTf z^w%Wr-~AQ_O+TqA>s^O;@;-8uQXPj+N9mAF!iSAcJHwON*FSOrVYxd8d zr^mqlXSZn^x&sm9sx(0O&oEtCnc>eroZ%n02&R+04OewQPP((bum1BhAY&YSB^~_y z`=_!*0joe^rxmiIMD0Jn%{P$B0CX=c)_ay3K^C5js@n<{9Dlxl0G{((dRbc>0t(nw zFr%y({2!5E%TIPh{n_*i_qv3YgB)^)og==1pH$8~p~P5l8P z3~71@mM=XF%TEdh`Ntmt{w<-zh=x=N>DP;4_a9Y*Zo4n;i(+^G^K<|FlGANpN@gUm zk^mZHK=7Q1U9i)}$Vl?fAJ=;Z>0^VpKj8*_D~DEV@PKl!7a8w8X>b3p0sQmAU}$}& zSeD5!3>&ckba#tK@3xmW0U5)8kNF>8Nub2KbpxKQtCMbh!SY=&EvNZc)fC7J{?`Su z{{6QSGk(&bv5 z7z93opOFm3`18YQ;gq3A4szZZY*7E_d;Ztu|L+X&05{1eM+npX-H*`^l!9dFKT3B` z`OmNZbMhP@=#iQ{NJojk8)gLv#t;?7Qu`u*9;Os*Edboa2(s+;cf+)##DF6$^?g@d z{&`pMnE&_ur60rxH)(|Roc#O0|K~G({8$Lcw{nbcoB#RMe|{#N9NfeM9=rc{!%VOM z?<~mzJ82y6Z=Sdm54g!8f^F6RUn}e1$HUtT-kD(U4Z%{K!L`#?! z0%7XzQM3T^+w3!CGq9hQ909Pq|8o$XkJ}-T0_Y=^Xexm^QojghT^LiGLhom_6u#WV&%74Kh%#dCVf+hJ~9c1h+x!mQ&gWc-XJ7B?Q>P=0WmH?9PAfP@kSLfKB#Gk1aO|0 zAwes*e(q-Z@Bj^=tx||?dyLl?mU{oP(FtGf^aC&dF)ZfxpZ0(Ph$tfo&e1(TtgN>jmI;eDk@* zYQwhJ5*%8v<+Un09)$L^(tQnNL<{Sum$^rQGVsoGf5LnDm{&$_KLSPppR`$hcZ&y{ zt)l=L)tv#tyhU3;oL5)Z(B)wY&+!Jk{R@vvL+~3!Xc`pVxOW|C&*yQ+RlbTfp}@6* zJn$&3jez?7%_SxF?6URT508$UfzogD#V`BM_rC#6=iv>|#m~+2@%8;>T(N5KyvW{X{{yC}REk@l8Ldu~ zq#y6T`iMB*_EndHM0sYDkOua zY&=Lkr9!9n+aA-4w7S)p8+-`{fDSx#vAtq@hgK7R3>1<#&M)V2CG#)Qy2Ji3L{`hk z;@+hh3$(IuujR$zcxg$~>Ba_HoNvd=rYOzF{^$?;x``P2r8@%+Z&wH$1Jb;%W?$GS zO&?$YxTfOgR~N^nv=?g%ACH`Gd7UkssVcH zSNZW)(hY)m$6^=j8r$v1yEeRbK=k-n*Z6WT{^Dh$vBcH(>nqQ@_OlKBE|E|=@EVKI z!h%l4*0|a~!|c7xR>{m;d~s>@xGMViQ_Pw$rCU%r+CAq|fxgtnUmZ`-V#H|4%u)0O zPj@zrj_aNW(Y)GGx%K_^)YR-4Jm{8?`xbY3mB_m} zQXQXSe}M`3(!G#_@6C(s7lt)yo=W}+O6`pte3?J<$qnvgTxu&YYj{o$i?pM_>N`qu z8?H5cHIXB-l3*rsecUDaAYAc zfXB-;r4*0)ze4=cYl4rP=b1#^=M~vhTCuf#|$pQo(d^6O}{UElqFFFG) zy8Rhf%RPL~YsC}PL?$nI&v!@~k-HsL;vvZbuh7y7%h9W1tHSRIX4qNZ(y1YXCFxMs zwaalI9LJp@87O>nWxpd#jayx`ZZ$;@n>|}YhXWmb>WnY2dCX=?K*K5r4=I) z(@_U<>d%2y#b`zcS9X6MQ2J(Hz3p%3f)Tx}VtlG?n3v0BBIyG*7+thLzo-HZ9JwR) zoS`E2yN&VLWH@v^0}idYk%RYziUw^-tG{24 zapWE5jMhCym3x2YvG&R{jTLj9_qa?sYtILEXl|o_PQaMw;X;glI2Th{0Z8jAOR6ACHtE{ z+aD@X6RwcG`Xl2|WfdW9rpVgij?)oU^2n5Z&ou(1FwShQNy+iX}rz83_McO zzMCJR2;Y-yr0mgw2BG5RPBFQn#W2_?eA!)NnyzL+2 z{ZJg=5GEuM(6 z)F37aakTh3ojCWUGQGfpSIsfL2WN1W5>+RiW*q;OnM z);$KGw2Die*!;_A4aQYX%D!5r!9Zptz4*>n6wE32Ks$3f^`y`0m7kCd{mC9*a5#xh z>3!z11qw~|*m!)VN%1X*h<^C-(;xJ`naR#*nM<36XeV%xxJlKLlO2=Z52ha`nFR{; zyuQh?^Y0Hw@+2NgZgS1qxkU;4s6)a?s;lZ$X)FuUHt4XpQ`Ia!e28{9yPhGtW7qIj zc5-t4C(eH)Iq2XB0!t7=Ko+AYq`5iWzvxJ1GOM0`8zVfo!-BM9BIVgZdMf7vl$Kr* z?FVr9EUxcI$2Vj<-HwKAM`=UY8bNI9LR8-wyc)J_tj^?YSL1D^iGX03%${Ex<&1i4 z8gO%i*|~kh)H*yn)M4@DhmC<-Q%!7ZGG03<$?$aLro0OuQL3@P%?vW>vr2Z5Uws=b zXnp$Sw(*ZWy$7*5O{f)$^9Azed?R26>w>cETs)AH-<1<3=amGG2%yjYa5S1$onkAUZfpY6vAkUamGK`AZZf0W-Z9BnE`(O!sflxb`XMAgc;E7Vnn)~FD3gk!Q7gQ!F(9o%_va2UX*q*G%ttK)d=^-Fq7!R+0Zxqd} zq<*yKR{b&Fu=<#hkQKRu+${}sppCd2e5>0T58kCQJCtL9!H#$aHp@aYq4meXjB)sJ zez>=1ACRyjJHkGs;z@Up&+QyplPKPMY7}+fuTbF+iL8kwae@<4lhQ{1C22xZsglx; z5WF919KnzpiKRi`K&LzbxZpgudp``3@x-WiUOSbiaHJlCLf?B9GwQts05PW^;#5W8 zSiTAkbjSoPi*s5CQLtD%hzRR-X49BWbKXFEGOJ?>{kBjYX zZv1?w5ZEb9b^oupPXnN@+ryQ{I2+t}@VGYLJO~%ct&MtCGX*Gorz|CZ`Z13VPFr-R z^l_mMUc>&bJQp=ZKpu<`JC-uh(8~n#7p1YnS zHBl@s-~AuQm-z+TFB^Zfxe=}=67^qMlN5}C4#O!L=68EuU=v1+PGo;wUotMnuv@@c z$5&`4cnsoZ(VUlCwX2UfiT&B$IM0(F*nIr_Xw|Eh^CS7mV6DTYx)NlUpy{Pt!%e*2 zujlTxI(UeAv@i0U%6LQ)Y!ajwn;I#1E^JC_b1ygIc2K)jwmgX7GkU)BUPor@7Ts1L zY?ju~2+>n5hJRhmFxZ`Z4C=^4$K4W-H~?G75&&C}J5YMnNQQ)`rR4d{2RQAt;%c0T zP>l=21?{HYW`X3HHu1J1Mig!at3D_~EF#qq(~>L#jNGTo8;L|xUl0#|T$g+D4(ksa z;~H^73^LNBJ*Kl!)4Ab5&$OW5G>GO=%-y;`PkW%N{+7qXovOXax21OB?Hy<9(RW^J z25x6WWkGR99_#Eu_MrCdv{#iAm%|_537$=`tM^FX;ywGx(sVvv2HJ|sC{uot+v|%D zI-&<|U1E|AEk2S+n2|2p7%8^_-=bx_fXOZK_xK1~3`iggkNW%|Xh}&I>zo8lkwGwT z-@~5Pnho!9DBkI6XK_FT?=QB89qg(zNYyXCMyJ}Kq|!kx-40k2>GZo_{{BK^tTEg; za){OhH)s3=)mB6O_4636Y!79& zaZZ+H6G^!Z1k|TvaTT=|BwTEG4DcrwXJtW3>;rU_71$FukfiGQ@wF~Q=6^yr<^9lT z^rcId;|bJQnZb_W3yLo8b4{$V?%tbkb^ZQq@hY0ii&tiJXL^UgF{Rc|&jBv9Z)3Ed z)UVAa*(srt!)t_&iuWF1M>%)FgC39@ylll-JwI_9_^q9X&7J!Cn!h)YNH1=FyrMEH z7QA!gPO;_>u6FXy>9r;h3ES|{5LibU&FnnujaaQ+A3lg3FETnTd=84m685kQFri|f z_B=n#mI|H@03#dr>y0i@hi5yGJvSk*p7ZP&brkbq#YyHAsLh*nH%@6cyJ1h-)Zw!K z4fOao9*W&FZQ*Z0`%E?6%8V#?od6+Bp+AvGz7vE5Gn!xmxWN?y2)o3|E-&R(A$<<4 zi_-W8?vG{Jo8wYqBwqTc1M5>vBB6ATgLA1h4zm(wnmsk|c@;Afza0NE{8XXEmTzfg z08AL$Mi}mj^xTc~kwwg>hB#qn3tONaGjTE~9`up5aJ_z?N7k2+ifsIS!R zU{;oB_$o}N!J}us+E$oB9cq5PH%YJXYa`I)^PXCkq3N7owRs##{Wf9|&ylu`8f6~o zx+54k6J+qF0sfkKm;c4&#jFM`$ZTdNZSXK~OWArX@8Mhc06Udx79G3xu0QuQ}fRZUia_unkv|HIYDFr55gNjl6) z@q~XfASoys7{11q_jmW>ok9b{Q_6zde+vxLq8Wqhn(BYc7*wGdgR1w>X8*LN{yGiC z(To9Rdh(wrAoPjS@&e5mytp&epz(*}{;x;pK@S)MTq$81tUsKu=tnn!W(+1CWxA*R z;f(qVK|PQHi~;nGqsV{ZabO5&;Jktm%@}-?-Iw^ouJ_kx)_lQDXs<0h|J^VxsnLwV zlaLkfzZoX|ASSqp__dz(zZ+)XJv3ubMe^!jnN8G~!F!+$r-1Y0y?V9T6z z{3o>VuN(h=a$;P<^j?&GEd;)e{eMBhWpsEL7xXVd!TUo10NNaaL^AKG2^Z3~)|^32)TO|N3w38!0jW6$JahxoC&ZvlYGp z(*IdywLxH_)atH~o^|{iw%@+OM~ROiJx_!cOj-#n0Hz)2J*7>Mkn>IBD`nLT;)TTQ z|MkK(0Zg1PYaV`X1QMh|^fC5y_SM<1>QWFKHH`rr!q zpB5cAtcq83geR&0BGg^`1{uV>6bExZ*?1&t%Eri#JYL$oEq7D%UEQ?Jmre*jI#YumF-vROYMVcLVS?fLblZr|JQ{3d7$dTd*PPOQ6pqp;}JUZIz^UGv$@ z_JPwpmvLr&ui=(4h!=q7N?n*Y z=0G0;@7k}5y;HOoga`Pqaq|@Q>`q{mn-iVuw8JQsO|ceQ$#%2^^i6dAIQl}~^l1{5 ze^T#NRflUto0t(w3a*_z>GCEXO-&kELQH8x~pll{Q~*a$2C>* zz}tWZy4lxcj!rg#-7CYzZI81ST#1)AYF&YPr7lP#W(U`gV!*uj(M;;I*?Lu7AEuh2 zXPxuDZ~(9(-i>R(^HLZ6AgCuQ&OFuxxk(C!wm-VrH2@>Rk34e#(kcSzX+&5x>heSA zrvaa%iqRJdAFVR42hjLmSG^S|^#u(lm9k%s{eosS~d~2Kn@6mA^DBu@N$;AhyC6QDD5oW$)grc-HGcS^Un? zI@BNbGzpJ|WJE)FWC~xu?$&qDxS+Phy#y7t=HS99{E`_M+pRKXJPf3C+KG4)(~8P#sB-3G8~j zra%NQ6GYw{+0`OJ^o>+7k(<^^#l0KV?3;XzOXIBh8Du4++X}|Qq2rJ7P&EW&d&2Kf z+YT^9gJiZq?-05(Dv91ol&Do;A1o)2uOxR^uoW^eMc@bH&6l59PTsqw`r}4;4r!Ot zH_0=jvzX^k(+*OPD)Sz$-FfF1TI3x7drYP*>7Fc$wSomd$i3fKkV?FRp$BF-he0nh z=(;(ocx5^1s~s$Di;oi#V19H6C2OvuV}WcO_`h41o~Jm~Yk z>P4p<@S~Uo$=&)VUfI$crOnXnYuguqZ>xDqNj{Jlejvl@?!^J*Ix6?+;sh%u}u*_7i z%_nn(Q)-f_B?pY5grVE$Ub@3P$%_Jkm4kP){mfDakLO9Sz@BJ=>OjBHpA7V7}I(q zaP2neqO(O)_^-@gYPu@L?}i?82Z+>x2FdYRx(gg5mQpmLa*=m_B>F0f?tssktmCnJ zN=?akuE4=`m$1#D?t})}_(kDG5@+r`?p7U$efPRD4?=N$p)B~)k9S?hc$8^P*G*&Z z2l?c)sq*JDXJEjk+Y%bbnoVf)xR1y80@Nug&ttitoIX5I*>I>n5-m9zU4d5uQQ2|Z z>#Ft3y>oJB2TBKrBj0jgmHJ>mLGCTgc&_g$d&WNLc`p)}@|EKIHWK1O_yH#+=J$KI zIzk4MUOMlM^kbsEWCKwn5c1B1+f&2AQQ%#$HUEHs$7lB)`EL>on`p|uXR45;FMve+ z?Lp@#hlKg_^=7`bCU<{eEOCYpTopRpD$2hj%`1qvL?+NNOF3`o8y|p_8q)CI&=JMC*Xb}}5r@OW z9WlOCQaZnEq)M@jOXu~{C2dNHA!lF@A7?@aD*hm-5Rk?$VP%RUmnySegn9L7| zV}d1@9+rAMoVgV;B`1cBr{ebP2@H9m2*9KZy5%*$Our~pgy!+tA?(Xw799@xh9J%k zn)RRjZqHb<{*K+MWYuSbLw-=hXD>#?lfeWo0{F`Mhe^z?Nk50gmmk?CIeMaEQXiz2ye?|aO=DlA z)6!TE;v1RSjNdcU4t_QFAUsz1v=vkh%y#oYv$nK4`NGfc8s%r9;5!}KKgkC7dgG~j_&Cc*;BmavmCt3)BshnY@KfII{~KC#rLF~4Qq2EUdbfA3dqP;Vl^DV z!m+Kx!@RGjA0Frv*vcv%(aqkG0xBk!y%#&3Bmr(bx?5568U#Qkhjc77vD}A|N4uX( zy+4+i#ip<@*QA}X-9HFrA&zT*&wgeIVLd%Yd$OxlkB#dPRq8GW;Zu?)FfCow0~$KF zWOtPR)TYO4de+-W!@b#5F$1vTr=HH7+1>l(nMW;{&|Abh-ZM7E;icn zBOmF7HF6DZCH|DDzO(#c?%YzmqdYcA;%!+{gcUur{=_58gO6)469!b`YOg3EDuAy& z;4(#mG-I2Cb^GGd@r(ayA4RqohoLj4b4S%tc!|I3rxy=q6jIl>Up{zgx|amKN6*>0 zB3|b5a3z-gp0xRpEXbaIEsx1VGJn_^@)lxn2NZ#DCG>or$W5q$!+@{v^DmzNEi-QDm6p@d16zQRB@i0CoJ{%KHVNad(xEGEIt`%H<#Lcq(;#=o|y76__xGIVwML+}knW=?xA`le4% z>N)FAQt{YFz6o@f(mAKKhD&|!t6~^t4gl=B9#u~13%>b6RfCC);v~iE(7`Pv%$PDZ zx|LI)BGMMoJPu6-M-7{>nm4&^dsstN!<*O5VEqaB9a9~a{IsY4+B>kz@r*yauM633Xr5Vld%B}g{ zV1Db<+MSC-;7C(w?ZWRUwjORX?2Z#QGYDUx&I%+@BW9ebVu2IW)vUUy^-?;JiGV^+|lDQGSG$GV(E|;yA zk)E17p{>epZc^N*H$?=RPmz2&12yum$88PdT>adJ#>x3scsnMsFe3Clup+Q0<*YeJ zS?#YB()vjrZAUyXs+8;KuiA}2?W<4sKV;;9c5q2dmH0F@kncgwzI97B=h{w`1>bBQ zJEmMM82HjTkTE6k(-gWdqjR)QI-2@H40&LuV^>Z$vq6Owe=4Y`aJu1etODVbh=V?LKQkWP6#oii*?Sid_1;X1uHmanfkr|$CGljC{knDKX!Q16` zFXMuDw%)40W-@+LQ8lvMBTFp-V)fn+pHFptb#FxHp!!xG~OvYrK- z;_4H%A-Wx3{rpkS+xHZ+d6iMSm2Lc78u670Slo;2KX#EHGfuaUKMi>27^$VH%)J}b z718>}*CU98DRL(9^C4lWtOSKrp$W`63rg21?lw59mmsT&5jB0Ms{- zQ1_aBoE^}A-`22uQH%yV<@#|n?){_W3BVO;d^lc}5R1|r!`X#tix5~wsxsIAqM@9< ziN%$(|B3`}#b8#I0>Z(QV`#U20GFR`G0zRBk0YRxXzOfrZW_VQWgID3w~NE!&FPH${+O+M z`Uw$>$u@G3_mKQ+%mf#n9^d>MQGstYmnuD!q%?G+aeEnJ7C%*k$Ts3(nG#yBd9mDd z$uL9ih_=BH*|@2(JXj1pw_Q{_lyoHX#C})(Y(7suf_v|_NYmpz7zf+vSmTR48OXZN zsp#p0jk*nvi&RfvUQtea@r}}weZg2Ol$qa~U%U*6u3`GLSmEt=I|M9{H63Pf^-y?h zwQMRyqo4AoM-^bynM+3Q^WdTuzvufYzf)zOA{dZfg~d7jKqX_C%7IqN@3R`^ z=5NAc%YzjO;<{%R6L_g?7m#0xyCvQELqA2<-{1$n4TXn?tmT&UzMC}rysN(Ugj0Op zdkAjs%)5A59^V*h{u~j089T?oHVZUk$F8Gi=k*ZgUW z+)dcE+}H1$5iH%4=W~}C*EXbLx}|3A9K-D%JM?prYM=x1HOANDhUJ$qr!DawdsgRs zs}U7vDP9Z}cST>xz(SrfS015H{uFX_*Q|ORvcm2y5Yw>ZWteUz#oW%JMY*p9yYjw; zV?PKVh6kK7Bd1>~@C=)Cnc>?ioAE5GiJOVqH+2%ye(#~Xcmu%~BO^wc_B~CG+zKRO zM>G`Mul0rc?!9@qhFp(J>wKLlk&!hYg`o52E(~EkRX%=B4Gt|DJg>S@i+<^q`V@Ar zTJntB2_{b$ung8k2d&ryqHNZNVaqV@wOkX7?jRG!t|GfR@8e&UbB-uPs==Wen5nc| z8H*Q8OT?)$Yj2aV%v8TPyz!En{zN6?jcozSps>85Q!0xzrmQs@-uGs+A=apYvSca; zM7{*?72RR*Su2j!HP#DfwW@bG7^Kn=X`G>g_Z7wzDSZYWW;8d8FGvt#8aEk9elP% zt;^!&u&)hHoRh_c3|yXgNd$|e#p->EW~SkD`L{OR_i1$renJMaf8O132(CX$OYJ@^MPVT|y4(>2VeSg&$K1^o{f!HX*yPk|*Hyp3Z@#&%Gv4Xfj z^|WXAOT<4U}aJad1*asK_ z-eHnmQZeFdA$xGMfqLtJVMfltBr>CA;bxvhoX1zbP{Fu3h6Ce`=Be3=aR*jp7c{UT zR`L|zJ%@x5M+U)90M8bz{0a$Bq`-x@qv(EM*DC4{`?TC@cxb<-pdbgazHP@SFvE*s zGZ~FPE5~2LMw*u4f(WjXp#)fwg^%ZeH>Fg=KFIrV_+6?)kiLE9D4b>5HteTZKZF)V zFpkjmsCk@ICVfu>pIlIOU>-Kc*Nj&lcdzk>nsLL(`1RI|E6p)d{P|$5urE@kejy&2 zueyczI_jNx++%#(gxfpGJHCR_IwLy@&QO=oQj!De2I%N2Utlnkmb-)P1k@??wAIlP zE1J-(ly2AKyI|B0vwMLofmy2)N_4Ciw_l%%HApwUX0;DpeSEZ?`ZGD}MRw3zxA~g( zPTKI33I(lkgK^Go%;k)!ThJR~p~{HnDe+3DpG04eKoONon-3^b6y4FDO(uBB&<{Q~ zB`%}fmTKei6}ERO?`<;e8XYGX$SxV|6csQ_7s>rAPNcJ4q%O~sA-o9qX2i}>J?SQ0 zz^dPDNOnT3)z4kntropcj@hA&g@5rJs`jAH;WmSI-O+M435~LoHeSETk&Jill?cGI zKA<>Na!2{zHzQuSr{!&ODcKOZt183h>inp^I}eq+)Ns5zdkaKmPdGVIA)JrVC9UmA z77@gYuzR)kXD91h@v_Pvf?(^OLEr4@c$Kp#mxHc9S@0L_q<{UahMV9P1trPT>)nvS zP^wkUXGx((+j_nIVQ14fh}h9eMOP}lD|azHx;34%mTm${ z3{)?;HnEKR%V!`L&W05m^kr(+1oY#GJR2HbW5^PA=QFoX&6n9XnRr!4nkz=CmTG%V9N z=NE6*6j^NCB_{UF%?%O%qXDaz?2N%r+C#9(TnXMv%Pk%_L*UCP624^_OcF_ZhzqMD zYHOY8nRI%eZU7oTGM@WICvZuT7)*V4M9nhCU6Mez1BcZ|uY|R{CnVEuU?G{BXN__8 z>X-pJt;f|VAx`eE4#rmcj zYx3z4HnyjnSRb0i@F%i`1YnMjxdT>{QOB#Osx?fDB@wt_VI7NOmbG>CYL2zBZT9*! zeJ8!f?#`NSGK^s^>BXYYr%bwIhjPJ#p@KD1VzII$mV^fpl+fh-6?k_wf8y*-0bTxZRc*mUPYQ#_4#ftCTuKd>7ItZ!Eb^DX0kI z{qX)kiufMl#vx}3MV}+JK{9x@!=ZZa-<*A*2*{B2;2ZOUQ{>0swvT2=Ec3u$c~9)3Kjy*1r7AG%WlQ@QZq6@iaz>cgBUo-Lo%mJ1NTM=D>Xik&_D2gaNgUmz>zybGYv1&5-a zyYg;S?~>B5aQ>*WOnpSNhLNQ0_MG!CAu%NH(*kPPaFWuY)gn8qg%=quqEhLU3fa^e z|J^avDr26Z=^$UisHBXpNTCK>j$cu&aomFKu2rBu%Kh5ObMA3PHrXT#)AX&T)5 zV83G&-sQDP^eQZ!Y8W)KY||ZJs3ph7=v#PPJ`wpqeb*03?)43me@G<8a?JgeY7{|pM>umK( z@pLL1u6py<$p|;`I<$WeeIsn~PAGBvRL2{2Idnwv?!1xr`TCQ{TqTv6t)-wC{H#J7 zH}k>HI-+8$Y(`t8GNwad}n7KfJi(^@0OT>_SIQ^@O-j%t|Crd+aw`qR5i zzDW&u^7HmWezAJ9BG+W5Jp&xyYi}4mwx}!lNdU6sV}D71i#Yy4atlPTzTMTAj9D#N zsPe5@-CzhjF0q`6#P?bR!u;f)S^yo9?K`>#IPQc$4#!G$^-}cOBw3M~`C3Z`Yi+x< zQVA|{N^-YuA|L^j79?^tOjd5tj+=_MWZI+m!|#Vw$K>wc-q(8#BjHdm)R@C=ee3)o zA-#|C9r6874X^ENL=V*rFT1ZL5y7#v{O&6b^Xs1CZ{W%@UsSidkkV{c9fp+HjXS8* z;`2YB{+$t#Ih|AARWHAYr@~3^Za`0%Xgk)9OxwTEu3ndiM6n(YM5fXTpoU*e(d9Nw zqLyk1wC-1)d$1z&T`j!Y?jaPHf1MWO6?wCaRLUxsDbni%=OLXpkKuW84@BYFR-2TK zRkki_y%P0dzKtl_B{5PlH_NUM#P$Xq0W30gT!x^CHB{z)q-NUPsA<8d^&N5+bNJ%Cy zGrn-s zNmYvs$Vst>J@eyRAMbL)ndAbFopt+jkGUfVRUozHk8;EI@|KN6?sFizZYrSUwVo&K z4z={(1=ZPwm;6)R1i3LfV!b)tkc0iUEwf4vmYds(i3b%8U(z;_kufC({!ibEHZ{Z< zOdJUsOyI$ny3XzCun9~H%Ho(Ww)?Iwvs4$|=Vg&#HonQEVA!jao{*oAlZ8lgn0i6M z%Dn4-I**-i(cT_2>=-T=0MizhDio~!X(mg|+S{8!H~_-M$uPx=TsI!@Uf8x%UE?$v z@jt*AprNB~GoKppG;UKSCCgC6>vMFrlPz5E3svKJ;I6{89J$7s%xaWbjM6i9W>9Ml3$JRvV!H~sonTV)k_Ej8aWMv;lsdjLIwCr!+Z|Qt# zb>dc9EXd%-yk!>&dAvKbo(XliA=NCXPt>`?!nh~+*4;IspGL?qeCtGmh2RgyG}270 z_dGJigDaO3ZNFzM?svKqcpcxM#D)vL-D?YcBV_W`PePCWc57^tTsD5^VV_nt&v?|N z^P^Or=m781-bEJVj)%E6Mr%Ek9}+=x>cql7Xz?(Q3`5r39qy@03nG(U)n3sC!qQK;r3cOpK8c*dpWGVS)yfIix@$` zORz%{stx7;<`C<$Q4>nqY5D!MX!W^@N1vSur#;m!YsC-bNq0nTdogz6P)R<=Ynz?jf^*`ypsr`>UgpX7h?P8=0~*Z<0?4zjVd%>iHq%|O8;P@JW$nNiY$@fp zQcJDE9lO7OqD9QaQ9gT`KTg8b@mx$UfyT1?Q-_`L)`E%;Zeka)RU^LF>S0b)*uPF7q#R|UtR zG}QH|_f)AqTW!-{Sgp10vq-w`2i6M~V#}?eweiD3djrwJyA>BGn<3b&pPnjE{3+Om0oj%C@iZc7I1=G|N-)ac`I#@cb^H=I?R zb*=uY#}J`L0?lA~U^!40LXUhystu7tq;k~MsC}_oZOos+444qYu-J%s`*ZPzp0W}o z<7aw%O)S!P8mCtN#oaTu0k^A~@^*(<4w36fFFsP=`P_nm(7g|x z1U|vHQv2*9ga+)*XT4m#6!D@koVyy@z=X;*v1H5U{HRF_*;|o)qy*98ws^C2)u^`r zI>D3jslm|FWp5}ZCcS$)W2e82arSPV7B?=V`2Deyk2YSvcsmQA7E(G9_U(7G7?8?P zx-MAUYu;cufo7lGv%oMjb;zz~!s$C=R+*rQg}q*irqCEGt!;CmeNVClTPF(s;D!SR zWDU;KN6OE2q_jTX1zGIO%>=VxGM^(WOt>sfBx^3ukA(~6C|BGb18dDlqPG-NxfBTe zcd)1II{M)h-wx;?wnWjGzS$3(1BZg5e`V%823srzd1Y*vPjKnuyCGu5^mX(N%F<3jP~33CBUnrf%X?K{6uon5Pw_lfNA_l~>#P2YJ4 z?O2h?&lVoHyWHb4Jex}ty_8AK(&e+u5?IfoTIARBq+A;|q(o_&opK^5!*;6 z#K2dP>8KuRBfKD5k9;5X3H~k{sY6m`c)Ku_&^Ur>o)7tjhrxfp|M^)lbXA!L4xuAZ zz|m=s_vGs>W`-rpoUGvAvhy;@&J@hZS3bYP<(%wTKjwZFeT_iS{|*hsm-G&*WV zc)Jti%UfwbPqFNqZ!;`SosFc;PQKMW@n(pd1Bfx3c%9HV6Wh*~rP74-tu+;sKGbuZ z*fa6e*q9s1q!VpT?Sw?(>r~VB?8YGtod@-*9~Qx!R8&!xJR`v&#^G;LMO?f@!;>4O zchcs=M5{|>j$6aUYRZ4OCv7#w1UHb|;v$~bfxax{Q1)-uN;rY8r6aQul&_;#NZ1Yg zRJ&k83bMPkC$#%r@=JR)15zl^cj?zuqn&2(npG6o=!(xP#oSS-djzxiDdQNrkWI$7WOeCN`=4jD;%9_fq0~0kJa5B<`r$NueqmTKXz(_Og4ATbz($z|=f3Q2 z9e3lYEXVo~GaM^X1T^uU3cBmQu`t{=ia)@nuz#*%;GeGW2HyFWYqiR$cs?o^*;n~Cq3x<|_F$&O$WG>%Wo9SUw&bH#O z>z=%)Jcg>^LNr+mS>2m0BK2<{=ZDhBwK{8Bxpb;*?@{@$5 zw`QBVcvPnKXhVPB2uY+zy0KhX2y3mGDpzv8vxx$+$d^ZJW_bQyuP$F}{GQt&T({n! zPWkjhWPMg-+aWlXXz`{Q|2VJK;-R07S3r=)(=&69WlawnQw?nmxCWeWNIDXRxJyuR zWDLoedRJvDpTp@M!JOs9M#3QNtUASlP+U!_qL^Z1PLeihWJkWEc(s+$_7xw(9Ll!O zT^#bLMJLq7QZ1)=UBq%-^ETW;%rNEHH`PqSWSvTcUW9GTwEj1_QHK&_J<(*_oT?KJ zH5vIo?7j70RA1k>Er>Am5DG{R-9rh|Fm#6jiZlo)-7PURNSAa90wRiZcS#F^AT8aU zK5P7**K?oeb6?j#@Vu_y#MjK8*?ZPrYwfi@$MNP3XOxKeU~27w75U1f?ueMp(U-c9 z{-G#Erw1v#51&nmGFJS!_vfG5)>c@vd|%VY5Z3G~2d@dvz+_+R5Z@`0-8yMYbR{G|@4dBxRB@~+trxMIg+C*qa#(opUSHav*2UqH2JbjBq zCNKFU@pMT%&DNLY0Hz?;Jd7*nINK_)?Wh74>4}07GS(D*m_c%jKaB9CGl#ph%;-LA zIoDv_c~N0?PY@>i3+59B3;M)KEnG zMRt)gTc%JRgMG@O0_CG&y=*G zvgm?_DU*#R61KZ^eF&P?LB6@4QKvgXztG?jt@+-*7z=ry1J!Nl%>H;jt=ZKmSty-| z98K)o_}`TzM`T?jT{aguK8az1Xu`6R)8*EHTA_A~@nSXfVSZ+1^!Uu|(fV-@lK2=4 zM(BQfUt!NW=~i;)3b_`J_{xCM{Ea{?=u!_S21!-m42y%yxi%Bg`6!upk!_Z?1CJ3 z7;oMu$s3nODk*a@ZHd`qi0%Xl;P8cVg6&l4=LiG(N}r?q8yx7V0q-Bw5x3>jZl~z- zLD?{5Lbhbm_sR$d{s`e9LMsdPd}CtzXptDE{L9=IZ1HH`wM02tclSGY9lpX@et8gh zE};|{)pst?%KMLmBh}pTQ~-v@%71Z}tJfq5LXzS>Ni^!$q_WzAVKN;gq2J%)Jk!hf zwpQq2bJ%`idG-ONLtDsA#(va=)Wr|9AJSW6nI3i)#oe)Z+yl>0kwzrP zb`f7^GN&?!&Xs@7g)0Nofctr7&XV;5%C+u3p9HQHCpHSuXx7M8Ryqzx1sGa9MC@6J zoHfR*QwGnI21(-Hv*9-@UT(OuuNrlS+Z_f-cly~rxi|rllTi8>X5Z$3I z(C2+kZj=FCtXrQj%nbRQ@lMCr?Uf%zkxS~NgMOLtSQ{*!l7Y*8spA%*mM1m z#m)Tyx_LTd#)L!&88s#N?y}*TgkjSHZ;71a_dlh}YWn^V^!ODO07bWJuurGkU)8=j zDvmK%hd8LMk8GV}k26i0gb1W(dwp$<^mg}G4Ik2+ zN5qI2tH@q+4--EvTB0tZBpyMv%yF`TKUk@q52K7(D3V)6)DfW#ms`!hI;)S!#z_{I zR>hJcX(uJ}Da_#7#EABZ2hP}Hx$E79Uw~n9>uYB}vB^8vg=>TI%jrQcuweauPDZ@5 z$z5tP|Pg!F4LbI6YPf?;2dkn-uDqg#^8EZcW*fp(&~3(z|2g>I+{Kj zt4N4*y_J5k!1m-MBRzijbP+pa(@m;02Rruyq3;#drSrW3s)e?>@iPj)RGTjIMk;GD zu7;;ZO<%>-R@b(Agn~a1erjq6dR=e)f>>SsRgD+RdiG;J9=`f+I%eYuTrv;z5;-NY zYv$qap-+ijjF_a#T!y^F*u7tvpJ>vJ%cu3|4OMgC(mYa~{Nx==VQUvLj&*^iX!Pw$ z6t>LYQW;p-)YXVKwJV4GCaY=H$S+HA-t}7TnjCDlA?OjI<&j#3Dttt{w;^<7fNm@H zOmNCpYozj@nT2vobHp2lzV%B_oy1(eb?7t ze&{pJMkAVVclT-d(1nnX;7{={=ByO_Forjtp&i@koajf+&x6^Llw!l*DVu4e0hegg#lYu#0Np@(E?n?ziSTKBp9ux z6dcl(Xu1fdOEu17u$YOhc5SXj<4+=kR?Oh0unzKgksDQrS`SnHkwH1(?>YLYmUJd$ zq24QV&ikY|_g}@+ck$=*eA*4aLk-8o<)3t>y1rjov)@X^FN1eE5E(_tF7`gnC?${e zllgCo&Q$+=u0Lv43$Ci&8WzzJW_lr$&b!WU6Ol1*)Nt|w)N$Zt%uB`F9`l5?f&17076ahzF|C0o z!_4jDhIkU85Bs84h|Rr`RThK4mty=Y(KaNOf2j6nBV*tp0gvs6-fJ!EBmF|DHh7lF z!v$iR)*hUPuQ z+?cycr9ZWP3TVht7oupM9X0XJe#^@{9*pf0%ZDX;lqqe$d$Oe@rr;6CazPX?y{2eW zlW?Bw8<^9>M;w|jZ8lu@dtcb9J*Lp5Ao?m5*Hm3axvbB|}nlzR$3l$H2i zB6&0KhcF%;xmO&h!*z%-Vvncv3BxlF%_ZAO?r>9m2Ef6;oyM|Q; zlTWXI^~db%GMl7VDc;M!dgY9OmmwM*kMvv2+lz0?2+Q$xmc~>CBPaq=&eZwL==Vvb zgt(-|P1!*Q0IraJi<8-=^ZQe9hl0y5|Dq5VP_!SGFK6Ongv<_3ugw5rv24_4LKvw@ zd+W4@5yjwjxxaI#OtWk9Fx2p4G+B>mYM7W*tF?@kzcs}3a&8*Q6bxN+N93+8Jqekg z?c__>Qi0@XG1S?QwM&`R_R;0)I$YOW5yshm1RrU11c6G1JYGiZpH(FlKs!>u=imQy6M-&D%Apx zwj(lrDXiw!ERAwokg|8H$?UrYO=al zP@o8C!1%CRhSZGS=$U@14JY*j=6rL^wK(X26XSe``XYHVkOKvKzJx~;>0bns%DEE_ z`z@8jje1M<MDEVWL_=kZ>< zR(_hmJpy!hEVcKB3wgIYC>d-YpA&@!9Wr~o0^eE<^65on7)yX@GA~Zr$DxY9R}M-p z4j=~kXe;f%oa+DuMx6)tHwx%ME`x%R$S-MzAz9*oBVb!N6;QfI_&j(-1Vo+D34~7OoK)<; zlyT&kXp5LcW}f?vdUKX_I9oUN;K{3)}VH5(F&|b=a!*-2cqR>|UhpTM= z!SMatA1UNuV@gd`i}Ams-2d{PK=c7vOuRu4<^KuzWX1$IC&?qyUQ{Faw`2dGKVG6< z!5`?9`5)ZkzmU_v?+B9y7qEj=j_-ebMJnnQ1M}(cG5r7eiT=;w|G@=~Oi(AwD;FF2 zKfdCBF9+n`uFd}sTgS}lBVcH%etcTfExxe{Z`fF`U&CLi4jiG2_x4%sBu_+2# zdWs?oEktlhP8*E)9OOQ0@*VxZIAxX^WfR~!KLDcgJ<#ZV`GB%BMoq5FTM|i4No*A5 zeGr{pSO2S0G=|6q%iLmci55R8B5Car`*U>|qVg%ao;NNv^bC-0Pe|~5X5v(sZ~3Dr z+pp?}0Mq;IxvpX;f+qZGs7-I~V&WZiG5o#Q3fp%etgAkWP;md9dD>ucysq)4PCeGa z1k~woiFy4HlWzNqH7b2n5Z};Z7&y*N9^a|oH4y)EK2*+s6D-t_BXYU*#bS%82}sYL zs=yna0V%5U1dyVyS8KsRUVQDk|C-aj!AaWnpUxs%-e?etc%Sa5{1xyTr%$#9m?J%_ zEKsUV3D5$Z59}}2-|MiX+qa>gW1m;tVtm5$3>q)D&_k@ksTH3#koguNan?%sl+yZ> zw4oBU(*W=H{chU1$*)Tdp;MRL0I@t~*mF)}V<_@|@7B1f?=t7~%VYJyiRjAl^-a^J z{_b<&v@|S%gA;gT(0H{1-#wiOYp^Bmi%{|-0~)vo5-0?%1n#|nYue3@9R2~LIlpZH z{7yPUz~S|Z51lXtz!z|aH(jhGR7u{cwgw;o>gxu7*14*W7kDBCCQP(ts>T$i!f(}uW7P~&>13E{}pTZ z8_E4JjTc26w|1mBy#5X(y$GX0P;ZO|V|`pw-sJfe=s2GCbAA~}7;Sy%7fB8tr~%#q zLl#EQw$;U7yw#ym34MR53_zSlVR7sZ~JdHJMJ!h~__Ae7bF=Rjqu?D0YWv-x2uqhg;E)pGOV~H0zT`5RjXdTy0Hhw%2`2o`R zM7mKSaT`DhyK{At!BzDm_mG5a2dGhV2}B_K$O_mw=-o8ouqB}i4an~>fmqsQ8{j%A z2U<{t$2tZc=G#`Xb&Y00cb^tPJbUiHn$Y&g%NkBXZwS z+;RAn;;3;_kgf^}bqqOhrrUu89)TFYCqNjHR5Mc4QQh8ayk5x)YgUqpQl~fYpmeWLUMZ>)&_haozeDyy zGXsQI9GlWHw4${h7I2*{fVjWs@6l{)n&Pui;;io|GuYhEZQ6X6bA6CZvS}3E!IVRj zkgr@VfGLheA;XD=orwrv$uYMJU2M{R%z82c_AZyUqF}JlwdpSOsBFNY(6hXoiXRDdF{$a3UX1H z_Ohr5)cyf}fG4v;__&`BkQNj^M%7~HUxKYl^2a8)uPDZ*40X>hW>zJ1$|^^dzefrE z(v2>-Ue0hx-_K~NUqio!taQ4!(|VHl^f@nm`5FrcHb~bD37ZgJ8|DHo4Re~J^2m%1 zH?tX1Ot6_!s3$SD{iaaQZNywRTn=jCEVNDwO83_oj+Quc3MFuch))lMTxe#a=r-2g zZ~Xab0&$RZA*Uv$PIrj)mjBoEi5<*%@dzK`c4wL=Atczay|E@G zx#RY3C4i3IxXo+7HFzAdH<@YUv{ugnq8Z=%swXhdn|4&8c16FR?n7P+24WY$T`yX2 zRd+itw}46bVrr@{=J%~-B6$%UxiF3{y4?xcSK`}N8YL)l`!TsLcS1Wz5Y(ggJzKcj zM-kxIdCYAj#~EAbI4=SzMxo?+ROgK`Y8}yRjdRZ5ijw8o^=(kf;z^IO-M?H}4!Ge) zTxOS3)mFmy)%B%fU0cb!ouGDw%sI4v0`e5{wFy$U(jB@ezN3t)>J-B}yw20es!a@1 zoCDEc`)_gD+Bfg{;9vKkWTJPb-z6A^CtwjT=wCqf+<8fq`N><_p#8G~8!h zO#bJRMRS$kLL`oIGA@c= zrzGUsS*7WYx~xp}lA((pnzJVD72PsLOpf^68r3tauiqWva?>N}qR1(yFN8xey6{=}#WMakV86$E@DTY@Zu7e{BBuWU!_HFfc3HNr^1Pgg{?u0GnR@7LPj=9toGVX<1 zC}fqNeB2b*0Gr2bm^m|LT*L6Owzfa^<{E@NnY;9w}~V;$bFV> z%)9TnFh$N^QNC}EFrgL4%k%yB?CkZItZQo)jhcB=h?WA_kH(ADSxFuRK?>+&C!IkEIo(-IJBtvotz+u64tSG##%4_aHzHc1!WImra-0sPWSCw{(i_G zEalgs8lQOv7rx2=QJ~^MPmgE$m1RD|*)0zy5ujhj$cuKFm-XV7tMh~R-*;>36BD?0 zB~A<@z;hsDp?XMAHIBQ%fVehc6KEsHC0fkvvOV(>H=4O6q^N^*h;h(2e@eS`Fw{%N zy);!hWe4=gE`~~a-(~NuTIPNHW5t=thEVKp?I~H(til=$+y?~2tf=C-YV{wA!kt{& zy%1-P>WhH$Kgx)npOhEWHVI8Zb>ShIiYiC!7Z#cR!|$d0tnkdPa+~Ih@qguW?7sSF z$AGMwdYR@Kko3BS)^6rsYeh5uEwWHVjC4(UorB6ftqz0pJ{@%}oSmKvBQLNb&6An>P75yK-9SqDsBTMXCF9yVfh-6b z&8yk%i9}=C%1uhpU7tV3yJu2nr0FG`h!MWSQOzpGSsVK)AF~ai%^m^7OtKtGf)M5y zdnh(>4{S8aChen3((^3pkB@lnWo$0x)@$%zL2GyiFSMNNBqdTo)(Ga=GJ2VD6 z&K&KGMG;UM?4+&?K!iB3e6Z}Hourv-R)jzp4~K0X)D1X=hTQVVIZP-?#+t0eHvDE% zrFL4SzuH|KF-{5Cq?v60!b=#EhfhmBQ7EaI^;Z(d*N&`Mulj>tuP{!wpF-PsonuNS zvshZn8Erj5F*#Anise6bx)e#G88>dxi2nK$6TFH|+^b|IKoB>E<;h-PS6r>jAY za0%8SIx@HyY@cJ8FAh^bFjX8?hc;~XnVZrp7sqoxb0{w{Sdn`sb3kwIwM?#(%c`Nq zE|qWe5R}JCYfHxndFZMpL*EHyjdwWRT~JYm!nwf?xiQS0{LFm?36;IO-G$At!Q$|m zZW?+KQB{z5e5d+Pploh5qE8~!bRMn^8PdGqG)t+PwyU3iRyp;#?9dxR;&H-f`zSd! zwMXYVoBL0CQTBie8z_O;sl%F-;U;On<8Nft%Mrkmzkz@3S66fve0=Bd&w7d4mF>Y6 zYQ}JRd+@htQe5q zi|FW?VfFvfMrjll-WgIQbztm;hrJf~iD0-n{oZei!dX$fCwR^w-|B$q-fzjnpiEEj;wxi4;9cyz4SxN&uU`b6}ay{dYDG z18W10^>f;kKXxKWO{-h%Ts?QWWENk;$mt+B=~y&fKeN|2bq-wTX-?U}-E-=?@Vw)$ zlCPg~N{-Nh<&Li&!|*lk0F2MB@&dDV9ScT;BoCqY*Ks*wh#Duz0giU>C7x zctLto$CbJ!$8+{&s@pOoyNe`Hr0+ALb2fKtuQIl0?^qI9q5l-n8_#tdy=ZS$j>F}vgEG4-&{SJ@q_fuimvH``4XjC|~?KaWS6EQ@w zf^}l~XP<|#@!#L%BtSL(#lnI985duo&xLf)tu7vuRbWA{!K5fGV~+--Xl8Ht-KXlA ziY#nnDPi8%g#IUg`efoWTlLi+CUj(J%V}uRDtd-9$m)OBR=uG9)vP2?RT-3zPI|CLZrj{(!NH8&U=g&0O>mZbBXpN$qz!ydgen- zDr0Be%f-y_G_eOR&Cx^ZkOei)XsS@N#Mqs&r$c+XkvN#YYz`oBuJvr-FkAoT-oQ&B z`_J<^@FfC-$Y0KakO@Mzz-&I(i@oS?ufJ|Sx-2hFjp)JZQvdyXS8-t_g#@wV+ApkA zc*f=5({`+zYxI!`h8)!#geK9j&60{17-EXpq&=HFUYX#fB_$a(Il&GhldDa3W@@0Od5Hb0c!Ml$vKF!HPj`>x}Me10I5o#)=$S5xX_ZRF*^!rS59m!Ev zwdBE^*QU?@cqS&}8H+bhPFUn+KwmLzD%p_(D}U%sqHCc!shWp119Cd_b%B2fQT_@Y zCJ$276gU!O-wYlI(pm)Dlz=r1D>A;g*+&z}5w)iSs=rm-g=~I`VY-x&AfT`&&U+Lllw_u*HUz@pyN-w+Q5~S{GzD za#4i}yB8FtG~A{mjuDiJ@IH40)0Vyi#wcdkACc}-(eP@F#po3F82lJMwE&06_iwTG ztm|y+y7xNTOl9`(>7pa0HSaGqj=CX=7)3Ljt>2+>{-oV*RBDNn91LuR-=)JB*w=b`#=XM z@w%J;s$cXfsYW53!xvQl%{<5w%l=f|IiyMBBZ>!m-1Wqy3lD(@=T0DzeceAr3(7?| z=alsJeSRcMS@#$%gL~QBV{x#|Z?x`B3>S?IXSs2eMt`pJo3zckK@B&R^q}aW({v(U zL4Cf^Re*6@8V&^aw-@%7$f}$vo0yC2b{+09`o+wb@L$RIncFu)Fx|Q>)+xReui#nh zHDU33o2y1m%k-E^CiuLgM7QbJ*rOWZ)A3*r$cty0O^Q6{uL)_HA&xyIf6Ynx6R^%A zXcs?U)_En8sZ1UPF()AsXpB8w$0p{isAv$s451}>)?z%Ba19#5-ZmPIl(`;q?tC>X-7-C^R<)Yk>OWo1f=oKE(|GwgZN@N5?4{R5}QA^8^^ou5lM8^`LEshn%n+>LFpmL zN8@*X-aLr6({{FXMLk@(%NxpZ4@U(_7r5-Rz{?;O6bF5Un;?RBzJQ4}#GOe0HgVrO z-H#kq!|!uW+J(`T>J^$jK*agzVSvmd&2{PINDph4Oa)ELteiHdF8vJ!C5W*zySp$s zPhP(rmz?#1!NEbxSY-LN;&gx>u}tViC+%_gsVVI)iIP>#=g7b##@VFG^>@wZnRGd)%{7bqtwl$vL$dh$0En=i*4)OxLj?3c8VG z(RB2skG({I+xXxi8#%{vP&q5_?Wk3S_#<|>;ox`DcblkDrC%Js^)5#v5sf$ zD2+S!XcY7n|Fl?<^OVL0HP$RbWWFGvc3z6_;Y$Y zwcK*=uO0<>OR#0Mk+h`9_$)1qMBBq*(;02e_0RA`f)V*6kH~KA)7ZhMZ=KY&7p|44 zrNoLV;lgDazi#YEr30C+k z&WUxqMs0JGsm_-iRL+VfW^%ZAqm!?Y1*x~`@M*foI1nRj&-OW+Zryo1-(sXj0|{W? z&n{^?FRX{h9w=`DvrNrhiWQyw2%mo#YTE;t`{1oS8^J~NU{pm#eOm4V)N`qkiZykhSA zaBX*d`TH_vKPv>L$n$AaC;ef5`Dn76b8|KPV8bV!(h?=k{R)=pH>*7RKU3``RT3f6 z;ga!#yl{<%61r5C5j=hsg35vNsLIt};E7QcZsG+aFC`x~*L1)m+aN%^;V4fAEc=>i zOIsuMx@j-gG6iTCCLR>K1#nA8@g*|Tpd|QW!RV01s)t2VYz#|e{itY5qe2Gb-e>Ck zvbafUFo!h)G(l#9QH#57pX%=t?>QjaJ9lrDbNE>5p8Z&;MZ; zp4_v~yx$agX#S>^mylM|w?n$WphzVlwTE&?W?8t?*Xk&ubS)X`$VWj%XF^`h1V(6Y zS6@wp<$paDk`V3m_qFt3!+cdj^0wYdhz{l7Gle+}4Y!g8w2C*3MD+~~=9!L^vl%=G z^bv@7#R;Wk&=&fLB|NVT|l5gENL)^M-Uj+oiyV0NWFo-izqy! z9}Lr@at0^E-d%Jl5skZ*HR(!r0YY1%R3W*W6;>ccuJwVl8@Lk(y>eohZwd$v=ys{kAU10=J)xsEc5Om^ZSA(7eEN=-d_wT|G81 zQa1EgxSMGYp?Ny42H(7p1(Z2y>+JxlhPBlyPF zj26k(w^WTVq-7HraUjZPt#~*%tjwCc*%)^p#~9HmYCmRF9PDr7R(Ubn_PPF#59Q#B zs+!welW}lr?VLwuYSXN~{zA#s_u@yt;Bs7_pKh{iStdbq@wB^4^`1G4xxVS*z#81r z+9IKw>RDyoh9b_K7Q;RjM%qyKgf`H%O|Jz863(S zqY9oLTOJukbO8;uv}1TE8{~_0CCXbxZtvO~%0lGyjDI}Kqm6^0!RRoMEt`-~s3&zq zq3`*tCs#!yAFKqD$eY`td%WFL@#5j}lH`(&Ihd9NBU}_*+0Q zzEyP7q!GzW6e(fDW;)A+W>&ESOHm?DaqFT_o{_Pp8hXFn!~%cpl|7ZSeeOXZ9K=k_ zF^EV1Gm#w$HU4yL56Wo<0#tN9u@RCxytIG82vq_a`7C3Z4kq=st7o3(ac%jMarmW1 z#90D_-sV|oaK^inV$yjb4Fy+KG7!0+bTsNu^l(&ft`&{`2sWAsP?^vt<_ZqYN+;D! zC=tP&!OQZJ#kE`BWC9~#) z^R!s%MLCFcnp#SLE*g)YC^a8niqYRIV-=8%n0|0T&6g?B7z%ZCjP?!~-x_yyh_3#K zkb*$`feLaGifAeb#8demtk>HyE`jIUA-?9}JDS5g+|hLVJ+kK0w}4jS#lUC2WErJU zoy!PC2F%G0mRB+JEY$RRZ$Ig@VF)VEEVdpxBig@=tIfo9UCIZ@qmkzaa$@R~-blUd z#C~w|H3Iv-qNPyVKTFf4B-F;d!?5Y%ocR+FwSti70d*&ajFkX9D^}b@Gmr)`!KQUM|W+xBL3)2fZL6uE4l|Rkda=dV+G?-EdgC>^ z_9ww;B7>ApfaYt`*OnuWZJS+FW)@UB%{J+)BJ*iU{pfmd2z|N zaa=+YzHc^IBk)nTB&9cI$pTyvdV-J{ro~cls5GHfLfOC6UzBK6bBp=yJCB%Bt#mzot^#^*qVjB;9|) zsUuUwd!zXMOH;fp8hXx+9FH`dr4*F)Ucu0h(mrI1<9-Ax{5Lhrs=muTVjtf|%!lf{ zO`5RSJ#E-hn(kWt^lyW;mCk+*$x*kke6iiOOD~Egw=i1ECuLl~^=h58*6vvNwxlA2 z+0i+v)bpv-`uhE<^?Nr%?O~k3MTtvK?DI}&k?J)P* zMq2#%KU}SrC3L&OsOtHE(~|F3C%M-V&U~$Qg?P|6RaVj>Qfn_j z##hjDQjh&Z5e2?SWU4IBj|#$E zd@TF8uKC)b058X1`UW9ZP0XT*Hm!1%`$om4w=$04DmOBo>|004_qu6is4P zj}YMus%>m;KClG44Jt;=Ws(Ti4C`deL(9OaCUNt=gn@|{i6UwZbey%Ln5Qm=>a?v% z!lAVH##>D26h*f+O3ag8(KuT$xE^&Se#41iu>Ml$^NRV8r3FQIcBk&w>mO$EY8xTc z30CofX_&QS=-{WpW5SSyJV;G*4#w*M*$|&-3_q`QJ&^Eh7*?C4Z~5gud4-OFS!NlV z9fY3h*F`uj6@$TT`JA-uZ5^e4P>ZHQb}00f;B4tV(+G@Se(TxNH;!-&OmuXI*GqRF za7`>3d`T08E*3t7`1>RM?686?%`Saeu)p~ysCUXiz%VyVfKHEmCq-CKR79ZQ@=W4Q z_REw+E;?dZ4E_8x_P%gQq!q=Y`0cxxoKK!A=DoS#!U(>P4hHwq7pAoM_~>|pxj|AH zOO+)bFzrL`XDSa3%lL6=|B#XH58%@EDP!V4f<6up7D4}^+9pry<5w9N>}kv415Ud# z&=2fh3_FS9?vsq0S4`%G$;-+$Q=?T?+t=OyFlgRpCAC#=XZ~6twb_#e1Cs&unLn6B zRuUe0GE(woNrpkm*&*3wkx_J23oAfM&A$Fv1SwSlNeAV3Q&l?Z{Ozu>_kKQ?)1Jdd zgYk?LL1E*v1Wu!9m+KX+?N*rNhiXy0f(sexu0-h8`o?fkuhl8;t`FSike`H9@1u+z z!az1mIlbTh>qsUB@`vg+c8weCe&c^8Zy+`$!A>fcnNu9|n0n~{FtXb34}Oy58MKwWq+zxek*>hREr5c)|A z${+u}^uKw9ETkCk|9;;;2g&Y-f)g~S__&Poe;xUMKm30$`+w7%{=W?l4<93#kk&R& zKI$_OgFJaN6KdE%H71d;mULk!>yxc1!PgUtzu5Jis^`)L2G?O*$~r0|+rFZaY=L*oD_ z`_CutiJ+G^9E5*M7d|}3x?lp+@N5MslX1f(sj!PieFDqXLzy8eW5xC}tv)+f_PC%eckrM5)Hyyn_2&nuC3RyGdwt+UjAOqCY-R6B4{sgMZhPh_erDrBW- zZnJw{Y97SZ=tajT{W@(rodN-wybtNib)U-5k7eC}=Wu^*;TpvF3_%PQ!{mFwNJ{7O8!f?#_N)cV8hE?E znFzS;ybxW>4%}KvHne3AwzNPcBW)k!-)w^lN>`A%`4g1#yZi(pVw<)gDd*NDzzcMm zcQ*=f; z;b&63hP6yk_ABUndtB6lEa3?4hQ0G&M!fb&-6EYyqe1QHan>BtkR|jBwAH(s2zJ? z#~LZisJNN0STye0H`WV5WK|5&Vxyl}P|;sgsPv?Z`kS+nWS43^NDheLk}T<-RfUuR zpM)!ji+(A0f@^q&cschm^#B+4clMrz_ZYDTiXoM%bZ?J1j>wTch;I<;;CdHqxsLw; zQ8fGcB-pI$7Tys^i2e5auC>l!Jlmr}Gar(OA&)i_OpjQc4k!lWePONV_|d$?HrA;F znc|{9ZS{W#yU`=>!}3oNM)q{WUy+)Ot|m`;S&bL98TUnPC^!&S^J2UGz0V8_9Y1x| z!+B%p0xA8~p$G+Pmjs*U>k}6`RL&Ecg;WE4^CKk3bAs@k_D2t@iaEJ}T6z0|U-Bn?1g8mVZ9=d@V~%j*(zSouNX_m2k`Ku4 zN~s>P#1ubYj(-LPp{thqx53)Lh3A`Fj1VM(#Oq4dBu77u zSPi*9sscBEF3a1{Fh`F(&>Z21@ti@|lo6pexTvHm@oGyviVlV$iObUMhQq;QxAkax z1muB66%9;7{P^+9etO^facz|;zJAVl@!Y&KkhVc>Xfd}K(hmx2*8TTz*@aJSt3OGn&GEY+b>) z1lOZ`gRmyf4)6xeo>qzil)L0zCJ|gORndDmM9zWCsORP&x!Ft)mRJAsjnF3C{AKGM zvVi~(fVpggEczbb@-KrTRRC}@^XOd0e6ptdiMsAu+An77EQ^s4E& z^OFr??65)Y%E6P=IPv_oxeN5$`h0Wi->9ScEz>mBRX6hXp(VCOjy$epSzR+6gq^tn}mHSyQorpxJYj*q%; zhV6obj6Qe zr+=F7u{?QMI3(w3?3f$E$Otx^h=fLc)nHG#HC*JoWlCGJibs-6KbjW`HMv#af{ttE z%cHcCpT?@nmr_)puQ#T-4giU{?Q*V<-gcEE@~$9M_`7I)UEZYcC!tg>Vs1b#1=uv~ zclo|nbyjj>FR~ibs_l4K5fuI%!YotFqhz6X_b2bW*q38O*IkNRsM21Mu6^oe*biN` z<1kv1O^|QJ<}$H#gM#2P$jykiUp4t6?viL)Kdbqa5oyC+>Y~sG8lR+PX6O*w{?>t(5wdypO6|q(T>spF2C$d)dzs^y59hpRJvcqG`$Pec(@SKA zI)51zqC6uO0?BP8=YJU~T5f1Dm~NGxpF%T9AZWQ-qe+%3E2Uk4@WOCa|9uVaqCv>a zxPC)-IbT@Kz1l!)49d3O)wr$;=~9<{I!$r0em4Y>O@O+a=rzh5BYRlV2t2r(X+ zka>w1JwH6tzfx<4?BfWBMhQ(v%7bOg(Wk?3&9_!xMy9iWodylxL;NPuQMBm8s;R-0 zO5_RS$Y)0~;gUe7)=xRP&)Pa(rr9JNh-)E?W)6jO^ZU~Uvj;f zCGwem8`qnE8yjScuJqyL`2|MhXU*S>Xgq5p9)5l(UC(PXU0EtNOhc+(y5x%5fmyVP^R)lbYoWXUK;~|PamuE{L;Zmrpkb6MG+g7^9fw%tq zo-9PTt`0oe%|o22VX`vrV2O7eOAYUB>RQKGr!JF)b}S1B%Zd^u;rf5rd(S|)zV+`r zK^Q>{qC_7=iHKgJ1fz{aPY}_GZU~}xM(-_pFVVX}^xnH@qa->ZdT;l3&iS4Hb3f(9 z^ZtH0ZSOsMU3;x-t@Zs}-}qxhwm?u9UZ~sWH3-;ERY*ah!UFyEBrFT^nbAycMvE-L z^Sp!R6t(j<;2y7@PM7kXyVr(B^nG1WK3utHdH6WxsW%pp(NE3-9}1+>*}Nj^jl0Ly zG_V$TR$oSd>55}{-W~DUchKSsG2t2uG=;kjQDpRd<=8ymlo3rbBj%;BzSdu3F=v)) zx#7xaMnC%heh?mg3IEG`HJ!Jnj5YGB7S367Oq(ol^*qvSnkXn=r>ORE50 z>CvZa?4GZ=Et4+$Bu_)Rhd0M=S1{>7gi|#;%Z^f-F0>3wr^s5P7|nejhA{ioN=e<= zS|>55$ktBY$L=r5t&e{e;6Y%-FcDasuy;O$T545#kv}X=+&DY)+cb5gb!4*w?fOMm zP~1x{DXn>`r7pqX^#^8_48M}T_g&xHMg=gYUv-e7g>`%|l}PxIrCbLL;W=ehuG^fS z602WV^S}8mHIej{I8OF3#et{)_G-_1&uv3!CA)oTM!uh({n>yU;7++6N3;59DWhP9 z1!BG2?sJ91@+-#Gb6q`9Vy|bsPsEB$vr;d9&a^GE+(MWOX)yToiuo|5k_=9#qQ=Y( z!dMxTp-H__xKmylE9}N6si{M1PSK*t(wqnoSDOOq!{o8e%_6DGCe!BC(ox#kaXw8f zF9{z#XM4}h&(haA$9A!X{hC@|A;Xp{mU+cpjXtIkE1Z|Gagw8e#G_PVt& zIM$B|n*Z3xmLB5=j`u?eP{o>;Yz2jjvC+YX=$&(BoZ#v|23s~u2V!ak!*%Dk+!)aW|^W{ zimbK4GMWvbS8iLEr?Yo|p#dx%p$sy_GNY+*r{o6GM2 zp-WDVuXM%=XUeHiyT4<#YV)<-(Hv2Mg7`r;hP)IlhR%#~NIjd}RQ+f~GHlz2WzDBN zf%zM@uk|+PNe7B|zJca^W!H?G97@gjQJjuLsk<4S!KG?fugaeP=S#$()#r^|)5G1Z zUyp|)>vjA^;Pb8*F!dy}n9-?y>(&?~g8^OvWlbk&0N5!w!dp6`TALhrX5=L+&tPWS z0J&&Gwa9m@IKKsn|8N$4_ai?!*7M6j-Fx)}n9VLi3lIdHxX4h-Vy(?oFu2Qgu8dr#Mg@U1z=anwx!qP%fayhrO&Hq=*yST zNFNbIYW(vvbhvgm8)!vA)VVaZ(mxM(wW9W-Gv@j`wM(Mkpl`+i5fg%$CiF%-u)z~w z7E1AZJbgEb2ujVTKHf9axuX6VhuMS3vq~wDPXLWzeFgqEHNof)a!*aKSbWjNsZOGh zu+ZqYa=5!kyHmmex-)j0l#t?`k}9pi{bCKS2L>j~1Q9oiCq+CqWQ_IQE^p!%kZH z*7>CkO30P|*PkfgWOU;t*d|4MFG;OlSt8aSAUmtL74NOI?_|- zOM(5%>Y8I5z0Gk2p^v$L37;TCP0@Qt6wE=KV_$3|N_fD`I&aL_>rK#pbw0MSDS70f z3#>r-=V7D$R!Col!wSs+t=_(bq1PS(lCPW?S9bRUppbL%9aVQpNB187CS^VH9vaw; zs2kUXuDhoFPOCs$d#dl#h3r=VNzUG{@`Q4P#L!eH(CUFn&{86gIcB$4B>5%5A&NFx zQlkDPz}B~7fagG*?P9p{){F0+#Ix46eX zd(%p(G%(F}OyAafN|Bq6u}Q{ThnW;n&D7zEUGlbPz7k*WZsxlj`@07m%sv=w5H;Fk zmy>!Me4P3E@#(8rnmH?m8u<=x5Lfjic>D89-`S427UKbOc%H>p=FrzE4HF}ssI!*{ z@kLsx-_Fe|{+0L#5~)VG5_f*5#B<`YIPtfZ({zwd0pHW$E`n~m&W@{(-CQrLKKc=2%$;mVltS1VECy$Lkr^E3D-F{`Yi=iG}jz|EA&p=oS{PAa#d~54_I=l z1L1<8%&8dCbu*eeIhYg+sux2TiL&8}`5eRRp(23U#_B<6t`+q)1DWO1UvS%$1u zuWO^2OF*{uu`s%!XyONB92z$5sDjG^I%36s6_qO}25eDGw5 z3-eBuAsP#;{l;_!_2R%dl8o0w3&rN2O0I-eB>f4rdr2`NrA5|y-kRzt2S zo{y+##bzz^5bwY%<8Zhz^PO8yl`#myNW}6CL1SaVgy2Mcp0sS0{}C7dkYo4pxWe4U z8$u{ceV;2VB|jG<2*k1)9W5varmU*FiNYo(3i^9%d3ITQo41A46YNLl_AtYb?u zNK!_dV*~B5K!@=fmYU<~lVITU8!c0!VFZK2TyOu)Wg;Q;)_v^fjnU6!skCMi5>wg& z+WAJjBaQ*a&wm-$eMOHfs$#z04VS5cAoD64eMQ1ySaUztEIN&%PJ;sCbj*qpKhPKO zNq+r9^cxs1bI?y76+J2V>FbE*KFKzXmLWhJ1$Njs-sNc zS@4_$X9^b-q|y_F{UP4H0h9a%?zO3thHUnc6xpwG5x<`waba{UoRPpn2|7))glfDv z!751%I)8Xr!=y1x4nOQiSuapObl-y57yY`Ja0;$ICZgNs?V8!I z8TPFY&>@Pg)z2QDIRNZ$NSu~hpcdT$;ef;E+phm6*H?z3?@<`yEAhTSEAQH@Ur#E$qD!Tlk+f1s&uHQ0k*hW^6foSqN${9Fdz0ExcZBk zVz!u5mc7q81`D7iC{B}@w9Cqi88~i{bKrpOXgV&qH>h7i+A-ME1u~zn7TqdqMwOec z1Ww1YymzT4Rb3I+{%e5;S@xO2i_yXuoKN63`wES)UA5_>^!K#H%aQjHuq`3b5vmBx zlQFj#5_L4lcnn!h`T-VKM$G~xPxeqpqfbKM^#Rj=Yg#~%FF|v^JB7?sICUhrd;A1f zQ4@yL05=#5F(TuCKQ)@>Xp0u=-qv~KU7jB>c|}Lqe`?w}6Rv3cwHyhAGN~y(N*&mQK4`nU?39LQ|M<>&9MZ^ACk6yCOpf^ zMsX%l5A?Xmu^Dj{FAK@6l=V%@SG!d;v~x2Z!?1XGKaNElEC;UEJ@;$l+@5Y8!t)Ga zc#asyu2%F=>NBAY;VAe#P8x)m99AKxPKYAGj6P@pRwArXF7pw(w#bsjet`dBm6 zhf_7iO_NVrpKX0m@@R}tKFT#s#t<3HURy-c;w>p1LQ=f!FaiT9E4o`Gc@FgR6XnB# z%k$d|a8-CR5Bof+A)Rl9J_R&~4Rr*oBZs&Swhlip%ykP%wk3Qys=sx?yY zHnVYcPed6no{&N?#Kour2?w6uAlRY5FrmW}6g3XL{M{^}M<08c1rN#+E2U8pL-jA9 zbxsf%dQ^WEZ;lKB_G$sgqvC>hcj7! ziRxxuR8LLm67HQC3`8MPEXDj#Osi?}oR*9d`NQAaX@eR=F19^>0HbZV=InX6z$&&; z=TgNd3V6V3z4i1GS9?bXt6hv0p5d47)nM;ZA`9HXz8L0RLTIf0bZqOAFw}pez+YQ< ztN5k1TS#pE7Ar8?18o_AD|+F$|&@h1)V`oVL{ic6!7|jOUBsH!x*XzYtSvpAgfJL>w*WwYbK=`qxy@c~ryB zn#ElbHoHgs8ERsrK^g~Vk!1ZM+vI*qLw!G`nWT}<9SaUbN50UI42U-$;h{rh$v+c( zhpExL7PS9tP*NTXQ?>Mwn9~6dhuTt>Hrln}FKMRVtQAqcFhF7q+t|gF-kKjJ4@Ter ziqZ{eb7z|L^}FzVxD^aT-!lw>p{0;cr{dX1g7`~EBIY?44UHC_ zwH6A2)o(##3fX46yY@OB-CtcA)<^F5wSS~Crd2$O_WMaUn2a~l!SX{z$E`<_-c_q& zk@VvG#(mqn-M2l4oV)vuFcto4C{7qJ;ODL<8TJ#Olzh2Py@3FN=nkbQ^zZ61e)0)# z?2A=3Z>Jp-@i;cKujVv|I6X7(M_lEO7e4Q(f%??TTTH)x@iFdmlf$}cs*|`$x#<(RhhUrwg?SO{&wYVp?yK9ViaQ~esG4LNpR%!{cyj^&6jp@)JTm9KDA zhHDoNo{SGD+AXIE)EkqX<@m;!YX!zz1RuP7&QaKz94=@T;uU%<-Wbpkw+lV*q1ke@ z%t}6Q(Dobv;p2agN+!eVXTj(O3(+%EK zj5$(O%S|YA$u2C(bw2yyiUXkvd@_s8aQD9Fd39he<>sS&%;p|pvHwL~~xc`8zhk=&t!mIK9U=H5*Q>}|VZ(`BA%ZJ`p0maE`Ep*B>AqZ3Ub80vnLt48U zf(R$w$}^-(N8u<8bwySiQ+C{^!@f|a)_!CiQc6wJEq2VbB>?eYf-+}iE&*vc9fuw$ zvl#)d3{seRUURcDQlIDGHQom_c_4ONS*Vn!vaaz@Ms{4s9$K%T^zmQ+C@@fM3X}23 zM6*VK4I*2Stb#Eg$ar$TkCq>Hr`g5Fdgx#0l z)~v)to976T$K@HKiX6CzzBfn}KtWObR@Kqr)qzQ zEfeR&{i{Qlbi6y5w&>lftyO^`MMu)I-AherCntfh`5c=-CyC%Xg0E*07EGYb$$d_g zgaA2t$*V&Qu4$%uL{J{u=b(4lUw@Yl%IgwB&E{0tAUyoE#Ae)J%$&3-VCQ>g#(;SK z(GWJ{4Hw1>EjfY++AiGbkUX?Lk zL56~DJ5bJ=8RvY#)reV8=re+H3B5VHjqL8u>v-; zP+Ex=bxjq8RdoL!i2iStY2?$`1&rjC1l!Rf}nbAdGJUKSOdV~F-sK?}?(7|=~GV6J-{u=&<7vsCm7394g~nYv4{yx@W()G2WPI|?{9Ic}^BO{dGJ<)E*rKq72#5dzgXwtbdPdh?eZ zP;A^!!q%9LM89~s>7gy^D_HmU0^sbe|BX+1V)s_awjr|gRw!oF@%^%%A&Rf5q zY9~KOjQYzds(x-<=?*p&u+e<_J=*3lmgb0>7j~W(%JT(}YkY*nyj#bkUjsK{Y33+v zhweD{xL&yfT*3eo@3$1R>VLD{+5)UHw3Rc%lHWGl!9b{+t@ruwEN{et40iFo+VxjH z+*9s1IXX|Hp1Kh!(ydUNRj*<34}Ih99Y%-)eh#Ae&=4;GC@eMowaIByn3 z)u;h;w-}E?R)nx!n6Q56I-GRRO!rw!WJ{>c=;tmnp%vIAsFwekV&qDLT8ANLA{8$9 z2&~i@;3i=~Ryo~~?E&rl76i)V`g*KzpEccwB-VyA3fJIUE(T#3V=94)A2_(D_XrQK zkw$z(q&=7hU78dVYb$CyWg8kmh?V*`jn!8&kd;OlFb>$-5_L;9=i48Au1$5eu|+&Y z%Yz`7ZP?P=A7og-0@mopDmckbyn`Cv)mmR1<=3eCftMR%U#>n|4YGJ?v)3N{=df&6 z^w7j3S8f#Ix(H+{s>@-Xsyl|t%?X3zJF0PN|4Nf~URaS$ekZr=^h|d@wXt^55)udf zhIE2_Kc-`xff(j9*19ktZ{xnk)o;Cn20Ez(z$i2|1{l&({2PE+HC2q3c{3CGTd}Z2 zP@RAgF$UMsOf&DQsWb|$tDbnGR?ra15{dRI(ufSG;|(;KGbL}+KE6EVB{oymdwCjM zdWqIc*EuG}Dvt;nOK16@J)jA;Nr>>Q?@X&M-VI0tX$xc5HIw!+{w636!DZMi*!q+LtIvATf~8F+ksT= z{AHPY@F!^9^Sy$1uOdyGiO14w$kT`_{^DP3ob;rNUr*%m5E4Mcz~inE4goOPAwsF} zXOtC7{{Z6$#_SgYMFpk7)V!{YifJh&`?(%DsFaW1ia&=m!a|5N8PViGD*A+7FCG-V zt!V2`U_`Oa2(F|`|9Hf3+4UkEKgl^w;XF&zgFC~(h{gQvK}tBfDx4A1#?RN$XxvJW zY`>mUTCpr3013{~j+!FBm0% zWL3@`*dj+pAki~mh$rLXU*|`qx!I2`T*UQ-TGZz_sRhMcbr91ik^aVpoWisL#|g}J10)br%5pUP-DCiCYO_fYQEEq`9?aYGu~PEXt1{g+nZ)vv*;-E0)aCdycW$GwJx+Bo*X$)GOkb{vVfw@enf2 zPs6@1r0V|9T16xi0@Lxdk=hlLKrWBg{x6WU>vX5-I?i5^cJGp1cRhrw`j#Sk#u{)h z6RLRxjjJ6uMbfKdp12u+1C5C_*U7Fq^*&3L7lPmTJ_eXB)6;kkY?Eblf2kJ-Mm7cH z);q0Y#bGj3OFaAb4AL4nqi8HYhZM0JGsw)4W8{UXQM9+vXK_#L zlSo_n1Y9UqCD6t36*fe!6_cq)6(S<9q-KhWE8k8zjxJ`}h*NxEL;zsQ+{bDKv!g36 zPg=B@QcUGxKJ4muktQTG@Na?ur7IJ(*M)^0}ovA=np#21Ip)y5+kg zms6HWMLz<>e11K`CrRq^--Mz08h2R>FL>%! z9D0mzqFo_MkAOp|Dn(O3IQ9W7glGRKKbNdh9y`uR?V*bP04D20!hZ|Y$%H}We*9(f zG{P)Oa(=D9#2(-=@E8(Q=geM)=8>sXr?R=uF1G-~29HS4#=W2szI0p?Wchxu3O-|S ze4Gg0^n38h)KuX=I_E$z`L%OGGTP4PI(JG(FF|%b7zZ;GfQ#H~*xURb6yA)L_WlZP zGGN8P#77HF|M>CU8ld zfO|Lk5-sJaf0PF)vir+8wSRPS4b7TItoE}i8zm5+`3>NyK3Vh7hf%Kl1tEV4&}NS* z|Gq6Lg=SD;j6T^8`7x@Ha$Eu^Q%p|NG7P zKezq=SHOLM>Lr9}8#5)pz7gefT#wb!Z8W+@LyjE&nK`J{wTRhhod4rK>#JWAk7BRQ zTM*fWGR4<>N!=-`^e%xuaGwV81Rc~i}^7ijx zcK57Jk+l`hh~a6Jrd74R#hw+nmC(q#@KUmHlIJ-H)v|utP^)dhz1|#sVc$B{FjvSo z{G|DMqv?7I{hx;*Ray+8^qnBi^PisKUl$@l=$-2zFhJdK;dBAmC&Ax-{<7>PA7;@PT*Kr!O=-BN&sjVWqb*&c>xl*+? zyLm)XYi)J-2DQFC;Zi^S|2#L9gkI3|Z{;KIf9?4036k6M6*!>Tr#fn27X}R8!Yr5E z&uRc}WdI;5tdFI@P1lqnF4gd+lc{5Sv$h8h*ePWt#Xlf#FaP|D3&0oN_euqdP&Cc+ zUg-e^_iIX!b1yn>?+dv%9rhQo#-AU}j(AM1Cz?<<0EJd}?n#^~oq;Csrgtl_P-(vB zJmOku0@ROI#B>wcBe)>3&KFGwiZENwlRkl;&plo8JZUPx!nC9lwTD-$PDOGSrD(XMm5y2;+Ge-5nr=93ta^ zFr_(V0l3ng#c|LgzBmRT)!v%{YVybB?f_mR-|uUmqYK6XYV&kU0MOE)WT_?9aneuk zcDz16I6X?gn5u(Ut=s(Ol1)(~6ymdm{>m1^elm<)MsAW56b(3h)PE3IUFPZF04( z-ZIC25RmU*7hVZ4TzKS!R?M++aO7u;PNw1db@0U>ly#b0=QN~*RnlVmTZW$qy64m*bZ=-epmeoN+A`ydYw_@{`}hOzOpZS5$WEQI zB@omUC{K{cg*xg(R0H;R>O;W%aw6vHoN$0X1V`XYH6djrBA!QQ7MEhi4MVkcVI^kZo7=tscz25WPAU$& zQH-wB@a@&|pAdG{Wjg+Ux}Q0zj1g=IU9ZEpdu$+tRa0?>x>b)SuEMZq4uB%CD0t03 zoC3}<;guGp^^Ep;SA-bDqIYhZdftyLr;0U@fJiyMj`FXUF8wg~{y>@vRrz^TlV3V`G@nGIO)zd2i4n%0>8d5iBS>rwUWTpA3m z&`P)MXOX_*ocYWgOH2U0QQy`|j)ObX+}F=XzqhujWE-`V2gIbay%+FnE}@xsiu0d4 zq{C}w*_)KO$=qvqZ^AEjR~nl8@)r4K|GFFJv2nOnm)LG>J8YKRAyFF|UB+Rb+Kold zv68LNVOImLPmn~VEi_Y|xTy^e_KNbv9Pu*6i0$v|{16I~eCu$NXsc}j)1w!0M(ER#X$8?LGvkdP5H-IRWBl79;F(rM;AC12M`#)i#rwT#5 zYC2ZwAfZXxT$NXS#%EAG+k3#*D7`*N-IP-DW9G}%PH6-RI`9&HBz%Mbbj>lyR#rnx z?_{;wXBHyHx|b934jrjw)Z#6py<-k--b}N~Ch6wt@=XIIl-$E42_~WPo~Ma(vW8Vf z(&B&r$;ZB8cze(z${pLMIHzyL|W7cB5Yl5@;&aWVH)T=#j{Q79b8ej-@N$&E5^MBQ80Emc} zNVhzv|GZ@qNBHBh9+H}dz2=7T(>Jit`x4&FM8D3*-*t79urj=+m*N1Ng%9+2%V*iZ zB{|t|=MU!x`Ip>~06+QJ=qxjUy5CM^YZf3N)7DVCwM$k239FlHT_{MN57bckgp@RB z*#zi_(pbuVilvtgJCz!#*?;5Y{j(Xn#74ncLz=F;HC2txN5evPsIIqS7&1Co8vsGu zaL`SWTlvH@_en%dbVF)?n{^hT)nz(r8{eGH82)+DdavfUlSSLv zu>^*iMC%#6gP`T;cdyJ^E`OF)Zde-9r~Hcv(nACQXpl7?1;1(z^Ix#Wy$ja1B$)^2 zxxsT%AAjN^e79fdO}HM?l!^0y0h-lcUxf)ZE+Af)L-ar)aygbFltucX1d?bqmD(O) zM`rG+>%-N)L_SiYjbm4Zf!CLt*>+3`)-DstTq^}+;*9RE$m|$K+IZf|F-Z%Q0hr&p z-~i4czK|epTDCCoWerMe08>2|xLU9hba6hPLswY&1U%J`;RH(3O!7$|*d^Sgsbd1} zso3W}z7s9YfL{ZtR2%H<-Wy}5X_^IT@)rOV>z9;ndnFF3R>ocdqS<>>hd=(m29Ra- zO_(5|?a##C)nXlyeORZoTKPq>*!^N@sho^YW**Ert%SM8;%{?(3MS(S|9p7QpoQ+( z-%b3vH_Hm@w9g8c9S34P$t7mtO7eM481VojUloiZ=8zfuyBc1Sq9A#30hsexM@B0r z42})E-(4+5L{u$tDwx{vY3>yy=lGTzHuXr)7kp1kilQ$eiwi#xJR0QYE?&qN0_tZXduU{g~NKYr8Earj5ab=T;gk8_=LWK6wx2D;}lxwml%!`?Pb z>1y0~$(_0R46aPJa2*^bY%nUa`os`wL}r#mK!wnT&4j{|jSuNA6K}0*i+MCR3>)Ie zN6zZ>b^YN|op6Am^k-v?H|_vR*dV|jNK_dw${}Zk>JmLPtcR<$9UV(ZiSM=C14@II z0lUk(y3q{*8+h)MZd=uwUHy~;z(I)vb76%)$AyP_7kQW8V{-Hl~1KxE;XH^fzsh?A3ncWIBlx>K1&i( z{5hiKJZ0f{usRKr;mqSUStK*tatIx9{6U2FpUFKI=$I8Ed94jkU1~Y+V&CZzy|v*p zH3kRktr8M4JZ24xaKO?g;+l`*tNsj$O?!_n*K28DG$aGGV1Ct{RimgC2Gg2FhO<2hq(q6W-TW*OMssrSm7 zFSkQS!$yP^N+w;e+tNr*Yl0C$j0{-ggDi&B>>Oe!4h02zpfr}NOv{4{;v4x1F?ZiV z11`$v(X$8jck8Qz_tt!~a0Y}yVy>d31$W#>{-e3BN%v=f{D5XR?*j{cWYv9^=!)^4 zm)*rJ)^5T|Bj|k1Dc!81TaX6DHn+(=ERa2ILTUL>Kccg7xp{W~oDu2k{5`Y=RaP_Z z(0TC0T}fF8wxmE&xAsjlI#vhmcRf(3ks{|^tlFQ9_#)#u>g}IEVOA2~y@O<5QZmxI zFX!mHGJ*<+XJMrsJul5e!l_M!tew0{nr5kxzZ$2LnuII)q$!Mvr15GG*h9-^Daegi z%r*P{@sfM|!;<0ZI$z`6Pjh78>#QB{&TH#Rn~+koBQ|8xQ0?YpzCE81RNcYR9FC%h z!E|c9X7;ZWo&U^w#ZaJQAwaapDhcj<;ESbZqhWvio|+7UTiH&0?U|}041$3NI(4hM z3^&G?vAiwG9WN-fb3$`r7nb;q5AgeH76ph1TdB7(RjGq3up9LC$u zq*Nq9Q6r{Cw4J`TLFwq%0a!JSk(idzX z<_=T|Fg{?RGhHsQiPY6$tm-AXezzN}+&ufEJttUIN3@A!ilEWQvK{Ym-fqO`1Pe~d z;RztWsZnf=hLxct1npB;)lU;g1;pDp7vy}vj0et1u-SqAkpXuhM5L>XbEXH-=hh@< z{+U^f+B0QSB{&Q~_VnSYEkg9nJV)IS*ZYp(L-5h7-APgy&n>vz_e<8$1CzN>z;un< zb>r?61aYl7r}_kvp{0yZTa7Z?8wOmgTu@;oFV;{fLD*BK3kU%%krn`Rm$)pfKI>#5 zi;iEV!5pBnSd@wOMuv1ejr_=0g;jQA7luh9YAQ<62TaqGW?`8NnKVYrf4+xhifre+ zXQ=2cx*rZS=4tPO;%U?sh2-Ddd%c}xOmIcVliK$@5}jjXEt$NSv8vdYNRN3u$GEs& z`Cw(&g7x=r$7hhqK97<_Bt0RtGm5Ky#?Ju@PpezCVo>GHsBLzMA&v+R|02nItR~zQ zbs>8WHg(l*%Fc;CwdrL>Ylvgi*2H&{5Vu+RU7B4w9Jq7RnzlFAw2XDYhXXllDrP|xoDQW6XPtx&hcs%{cXFCNYPyDa znx;FHuE3nhd`{|Y7DrLkvdZFi(8)Gf#MVS{xkg#|_`eSf>-_^G$uKMZTk|9BtCXku zLfD1l%FBM7}6CWZ4gyPGsYZ>UL`&BO+EH zFQ&6vf>@V7sRveem`pn9V!*u=h>bVmCPH*;%ySe_i^p`tmCwAvL8t8bF}o>TQ!G#r z9exU|0f&fmPG~xTt`z(@?6XRk*J+7VHGz)jwGh(z(gC}g8pzvUdj9kkgXb--h)-(C z$yzV=@FiWTVP`frqu+gk@6n1g(@!e$JRUFctl0HumaOW2Zl072$G4&rA9QCxTvJyH zNA!jvPsfA!m%ho;Q$Q=UuYoyq%9{3Z1m7I5==*Vg56xK8v9iXc-PkyXZ8ytW6*%lL z{rF3mnH5tb9W+1Y=2I11h5>*$X9!7ZH4Qa45)8u&Of)|!Lda)52A!Up7aWQg*+*I% z)|(@Df4f?TK!R9WoR>pXkBKLT3c1GpXFpO{RY?z48a~nJ@)xPCT5ucsh8%z4IH$?i zFxnkn$Ywc2ZAdTu6B!;>Kp@^>GF!btiagr8i(KHam(cutZHMf3yDL8$c{4%nxzjd> zwspkkc=`wK{?n;9^`9xH1A&tS2!g3@+F1U)-4FX20|C)BvvQFa@t|V|-q}yw1_+8Ru=H|J?G@ zA>8ciEmi);>xw%wE&GKTgtAHSl2D+{_uv+fAll!N8g2n9Zcx0oLialS z?IFXSuoI)!+ij|j#b1&~f0=$Jw0@Q|hcW;B+iwNxGvQI7pBFB!!(RSK6QVX#ILaVok%`HVzEZK@w|ILk#$#FC*dFw#h)o}N0@r{l5+HR zl|b-TL~{LJez=f_P?`+R4*h|NA3kR6`bTpq$-M_j+FMwtA`R-|`?2{{sof@7Xi{dn zb;UWZu~j>&T2pPV;HYBZ5WYjLxRX*p#mG=dRIrO6MYY!%6~DhBx3Na<2Hq?9ASX}T zpR|9^*CI8WtA39~5m%JO8L**Ce6QXi&K)ypZMHNYgBk+t2@P zw3TtS?Y~4`Z#&NY(A2hEkr7p<_v@aT^Ht_sLhINaOT?LBzip0VGsB+=FIq6gd8J&{ zbDVoO8&^B{wrRTQpJ?!q_=N{Sz^Jx26z0D~T-uxSBy50X^64aK%cIkoY|06FZ3Lx2 zQtm&avkYfBuvI( zH^QXLH62Y?@XDfT?oXa0I24CijXAYDJZ3Gkd>81Do~3IuDzEbcFEd{){d>@|N1d_} zh-cd4S1LZ2rfk4fsv{d6!a(r+iO=H{*d02rD{e`@7(F6CAeOzFLWOIr!dot9(AS;# zVDHO7t>sS)q1%Mj4eTU~=98#hVdqtPBHHxM_wgx8-7Vf?pj>ZCWRfv%V@-%EY57pgB#b(j4FRBQvrIN^L;k;Nt zBfSa6;+2H<55SalBt;KBYX1`%Z)?Nq9dO|I4l|8xX8-Cv-rsy;z3)AOyM-2OCrmPL zQi5arY*-B&>_-PwHJs0!at)s>tNVcuZJ>#eGoF%pb`>E(kIblS%VhXQO>Hw=%5|&j zyJcr6w!@#Fd$m1jS_5htM+zvt@pQ}O^DRDu8377zo5nCLgRgK_4LHTo?tA#E-N?-T zK{mXbh#Nl5zF<6&PN9DkS`4@Ie9g`y^53b6BdAdy0hYBj^pGn25w+R(XNUf!v&TKZ z2&V2UBC{IXBRiwx-#QY`{$}~yfOZeH-Ou>!TwNL@HRQvyljXVts?Rh8q!8r%2b!=a zJL0()U3|PS&c)pJV1@$)UU#}EU#A9(9Jfl1z=*J> zpuaP#t;>T;3jOZlBIHHPk2qY_sLh4LK*V8=DNaElR-B4QpiGB9dU3XE?(YQGX{`YL zZ!Iy&9~(LK**$^|=^2}VQVUi#<%!7B*<*5kt8XGVt8@;ilf5$SX(X|Fx+MkNF_Eoq z@H54mJY<{YvYTsn}DY4%@3NNmzf(@(-mj4iI`?8Swm~tDeaPn(zuIX zer)=0+bd%%DnSEF&@3rflO`oqo|jccz;Gp@z3K-+Oz2m_;>qp1QhGayyFAKDYbLGB zL;^b_b*X8a!=CW~ctIO{l_hY2k2o-vJ3a#;z|v8&)=@m~K;mL`P0!C_xMZkg4Dk&J z+d*5_P>}0$K*~!9-K}<<0k?;%SbiTRe8_tHv2&k`gejVH5Rk)8#Rvm@6K2V?&e|}k zaZEHe>wzz);%zMoM9`VK>=9_;G2WxGeromSh~WOW4fb!M;mvT|Fxn--#JLaVOpPC< zFMYt~xx1SH;F1>?`pP`w%6w26(Efd>U9)h&UXV7@crCxgChQ<3bU3kBolDX%zn;oF zZ9L6WB%_#LQ#8VEdNzOl%GGU-LDMBS5jlmNMzvckYE=n#PL!gc=JpT_x1$EtqKMd09}MMOZ^0uaeILJ}_X_G!9X zRWE#RD}Vmjq$yklKZ?}kbhl}M5T%!~N7F%m)uGb6g1Ve%%;&U1)IxiEbuOtVyYyD0f+YFIPd-!-toiS4OP-KUc!#LiSWu_&pga+d;iT}C49!;NNtq9jIQglmo5 zYtB@Sq;rq1iw{HXgWyy1vr}5P9mrculR4WXQ9bslP#8XiNQ8V!t3C@6ydQn9+~j-n z1W73iCRiSjXaj zwRh$JPE zsW6soAv5-`eR+d+z7F?mhRM`#GQY(idW+bZuyJG{ltfOF>2h zRRz^>^G-IAal|&{oN0h1a|myKBfm1SHb|LB6$|Z`W0Y*=Nx-0bA7uE7k@`JbvD5V1 zX`I7&#ZDvfP~+0`GYxFW5D`B)5gL0(WKAT6V?^|2I7cL7FnsAU(Eu=1n7Qz#Q>cQx zu1;3?*t~=1hq}06+1>J=L*vN$MgidGG|`N>4LD$Q`D-B!2I9+eOdp_6v0Q6T4n4@lOC4Q03Z2!9;8kCpm~tbPM{RWg0hKjd ze86==%4Y%j6E>LmP~ue%ybjKReQkrF5Eya$I*RVqYK@-**^) z$pR-+%^YgnL>Hq|?6m{2;-wov0c8GOR-YNg4rTxCR)1JK@3qm!Pccb$-Wk!CGqUcj z*WHv`!O>`$+J_6RX3N`Wf|jgH-PcuVeE~uwC!_Y30YTEoRot?k!Nv9DBQA{h{{si& z38piG6nRktkNS-Zbe2wKrF@RP&NKmLE!Gy;*@uLLK*f5$|Y{M@!j-etfOh znZtO_Awv!S53;)7=aS3 z$G5iWi;9-Mwlfk&b#QKXVE&M3K2L5f+?jW8?IWKlTdGLiUGK&J+eq^&Ugi$YDU%p0dINo={ET&@c z-S_WUDNd^^GY55hvi=15bIS z{_fqbH^WTN&M<;6l2%Q8t1WzZULgLU^K#Sa42 z@AWs`7vpooIfVc0)RC2(1nzlJg|fZFT*vlV2_2o_XAU;&#`#}f$P4GVTec9G^*~0g zV!6+SEo8Vyzt1_BC}YoirA{ZR_ z<1nCRTWf(d)?n9a*VIwOD#JPSdhV*1MwHwQGZ2YD+ppqj$H=FskJL2MddEQSI2`8J zIoOF&L#5A8(wN8h7v$djOS z9C)LNq#KUkcU`_Mo=B8_-H(3r4ex@%Ck!@>z=(<*$%XsfrXve9&u2wvyAmn_HdL zwMgdmhs>Q)pvie1-DVS#Yg2jqC6Y8{33XkSd`k;X@y%zN?d^SRv!+E^75LIaa2ax~ zMRpKVCFCeGmFNN+-(~^d&Z1aKNH?;t%_7f@&B`zRL*HWTfXgYBx$pG7Ih+`#@>>=g zTYn#dJ_#Kc#>qRmu5YLii132QTr0; z$`2s=%qtcW)Y(OiQh{u)ZEUTsJhu>o4ajfKpcIAzGqN8{)%9x#tzq!xzMhXotWI zsGD>g4IRC>tmu4<>%Dy!QpM<*O8HD^p=RCb&f`!j|9fSj8%$_qz$ou zYnb{w%bKCzAM<+vk929)sOl|YRhWX4h)w`EI^a;9iKy_%jfV(ZLj_$3C$g~kQ0>wX z7V0|Dvxc-hPAt4}QcNw&s~rk#cE$5%zN*B%@gGXJVWNxW;?yXM%3xT*Nl#SpIw2s# z=4UWAWAfMaij;B-UubRDPyE;qD{F+e%dcBf50C5S4kvJGgm$}Ce%7OOCWdl2IcKsj z*d%zGf{d_KfAzjvN4Fj9dWztRL)cgUS(za6UEi+Gls>}Zpu4?`en*c#l0s%8z2wDh zhfBR#<*^DHXrxY=cNDq2E^F(kTv91r#Pu%MwC0^s!EBRtGV$-9&IqL!2Z~H-7WFuMMbo7X)Iy3Pdo9 zC47x|Y$AH*W}vmlD9PDaSW1D(nlyDp$2HDD>+uTAO zcG=12MVw!dhOacmn}v4w<`3f@n|_zJ#Fn*o$E5|L?3kpuV(;HS7@-z4-pgIfw$_+Y zt$XOHDadG}!ua?up!gU07Y@@{zMnJBx_4iZWTKTm9mo)qksM(lTcf88&EV38z*^|| z9_jF9`%WL|d0P}cN)b6s4$v{_Q|3iQ4y&O($7EqD93&%w$Sj_Wu5z&C4E5h@lhsiATf^toa3s z(>lBYF!A6uSWL!>mSA^8x8dTA;rb#}+ozE^15{ z?8zp~$3RbMG2(3`3w0QX2@01ptrMcp!?3*kgaM>2ys&z3@UKP-j^{C5M`Tsf+#N-8 zrAY5@d6cg7uJb>$hc;GR?p8`g*(auc`PO0En6(|?Q#wYtPrW<6gF0C55!L?0X25r> z%U-OHlQN5L+?rwyxUzg9nvnLxQDheas&Kc8Uk_3gN3Y-kIgIU>4#s%IIq&CQYi&_J zQNucUiHIY(E37?v2UUT}uxczO%HwDzNvOK#Pn%V&>=r_slic&@TQRy3N~-+IB8J#< z7!AAg0)Ja;TPFNkSHtte28kOprFjpJo?J**eWuAWudS2{p~lYj79f&QY7a2hb}Uc_ z(v8zN%rwEJtOzO=uSF8l@FtiVBmT%n|D}?jqO~PHkWuNJoZdlrQ7c;F>~}h_26KSZ zeB?=7X{S`>Xa>6s9oks)7CP=Gz_Bd%Y$u&}ToC&EAdRbO$dFw5vRW2ypUX`Xx@wlY z5B+Iwp}(kW@ZwCBP=8MlMgRO)3b)ziYH7T_O?HQT_qW-?ZbM!ReM<@chy7c}>XLm~ z;DawxNfkf>${{mVVMwoJU07)on`Jj0OG@X)cDY-=2zeny?0+kvIbeS}l=R`ALQ4%w z)^#|j6ze3CEp(;zm2|yW`G#kTdxC}BtE}VR-Dg+&4jNTGC~Fx)lhStz9HV?1`@~5DwSp;SiNrErgOq?^ zFKYjR4w9m*U?X?5}`Qs)yW>2*I1vV!&Fm)r%LC6;Un;TVXt&7u0)sKa`_#{2@#~>ooJKG_26Phs zw0h6G;lHC817n3Bgy;W0^lw!5(Osg3Qb>Z*9(sEZHMxhB%K-54A8JCsbp1aZ3)sG} zbGy6{KSoLa-&^j5)B(@wkuV|Pe?1JeSS^vLjkcAtKaAYBJKd8SVGa`WC^<6g_XoBsa+@$;!? bKsHrT>gbr#3uy?L>h8Y)zGaRk literal 0 HcmV?d00001 From ffcae2ed2b3588312ef5462a747a090a3f44ac0e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 16:27:31 -0700 Subject: [PATCH 085/118] compare new features with existing in a table --- 04-materials/05-developing-with-ai.md | 31 +++++++-------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 87981671..096acca2 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -566,29 +566,14 @@ Once AI can reference your project structure, coding rules, and build your proje Before we extend the functionality, a quick reminder on what the extension currently does: -**Current Features:** -- โœ… Displays random images from a curated collection -- โœ… Shows captions for each image -- โœ… Refresh button to load a new random image -- โœ… Layout restoration (widget persists across JupyterLab sessions) - -%**Frontend (TypeScript):** -%- `src/index.ts`: Plugin registration, command definitions, launcher/palette integration -%- `src/widget.ts`: Main UI widget that displays images and captions -%- `src/request.ts`: Utility for communicating with the backend API -% -%**Backend (Python):** -%- `jupytercon2025_extension_workshop/routes.py`: REST API handlers -%- `jupytercon2025_extension_workshop/images_and_captions.py`: Image metadata -%- `jupytercon2025_extension_workshop/images/`: Image files on disk - -### Goal: Add Image Editing Capabilities - -We'll use AI to extend this viewer with basic image editing features. This exercise demonstrates: -- Power and limitation of a single prompt approach to building features -- The iterative development cycle: plan โ†’ implement โ†’ test โ†’ refine -- How to debug and fix issues with AI assistance -- Monitoring AI context +| **Current Features** | **New Features to Add** | +|---------------------|------------------------| +| โœ… Displays random images from a curated collection | ๐ŸŽจ Filter buttons (grayscale, sepia, blur, sharpen) | +| โœ… Shows captions for each image | โœ‚๏ธ Crop functionality | +| โœ… Refresh button to load a new random image | ๐Ÿ”† Brightness/contrast adjustments (slider controls) | +| โœ… Layout restoration (widget persists across JupyterLab sessions) | ๐Ÿ’พ Save edited image back to disk | +| | โ†ฉ๏ธ Undo/redo buttons | +| | โณ Loading states and error handling | ### Power and peril of one-shot prompts From 05b652554aa8f5e5ece3173fd4ae1b304da54d2f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 16:30:17 -0700 Subject: [PATCH 086/118] fix wording --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 096acca2..5b827aa0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -8,7 +8,7 @@ ::::{tip} Outcome After this module, you will have: -- Implemented an imageโ€‘editing feature in the demo extension (or a meaningful enhancement to your own), verified it in JupyterLab, and committed your changes +- Implemented an imageโ€‘editing feature in the demo extension, verified it in JupyterLab, and committed your changes - Practiced oneโ€‘shot vs structured prompting, model selection, and context management; comfortable reviewing, iterating on, and rolling back AIโ€‘generated edits - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: From 55bf62d6b6b6db7bb2477373fb1b1686d25d55b5 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Wed, 29 Oct 2025 16:31:07 -0700 Subject: [PATCH 087/118] more better wording --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 5b827aa0..6609363a 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -8,7 +8,7 @@ ::::{tip} Outcome After this module, you will have: -- Implemented an imageโ€‘editing feature in the demo extension, verified it in JupyterLab, and committed your changes +- Implemented an imageโ€‘editing feature in our extension, verified it in JupyterLab, and committed your changes - Practiced oneโ€‘shot vs structured prompting, model selection, and context management; comfortable reviewing, iterating on, and rolling back AIโ€‘generated edits - Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: From 5326601a2790d4ff7b65e5d2fbc5075062b9e3e5 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 07:01:19 -0700 Subject: [PATCH 088/118] cleanup --- 03-vocabulary.md | 6 ++++++ 04-materials/05-developing-with-ai.md | 28 +++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index 85c16317..16c7e012 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -111,4 +111,10 @@ The key differentiator is **tool use**โ€”agentic AI can execute commands (build, These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next stepโ€”iterating until the task is complete. Examples include Cursor, Claude Code, and Cline. Unlike chat-based or autocomplete AI, agentic tools act like a team member with toolsโ€”they don't just suggest, they do. + +LLM token +: In the context of {term}`LLMs `, a token is a unit of text (roughly 3-4 characters or ~0.75 words in English) that the model processes. +API providers charge based on the number of tokens consumedโ€”both input tokens (text sent to the model) and output tokens (text generated by the model). +For example, "JupyterLab extension" is approximately 4 tokens. +Token counts determine both the cost of API calls and context window limits (e.g., a model with a 1M token context can process up to 1M tokens at once). ::: diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 6609363a..4a4731bb 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -9,8 +9,8 @@ ::::{tip} Outcome After this module, you will have: - Implemented an imageโ€‘editing feature in our extension, verified it in JupyterLab, and committed your changes -- Practiced oneโ€‘shot vs structured prompting, model selection, and context management; comfortable reviewing, iterating on, and rolling back AIโ€‘generated edits -- Confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code +- Practiced oneโ€‘shot vs structured prompting, model selection, and context management; got comfortable reviewing, iterating on, and rolling back AIโ€‘generated edits +- Gained confidence to continue exploring extension ideas primarily by prompting, while being able to understand and edit the generated code :::: :::{note} Inspired by... @@ -62,12 +62,12 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) **Frontier Models (Cloud-Hosted):** - **Deployment:** Run on massive server infrastructure by model providers -- **Access:** Pay-per-token via API keys +- **Access:** Pay-per-{term}`token ` via API keys - **Examples:** - - **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per M tokens - - **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per M tokens - - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal - - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per M tokens + - **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per 1M tokens + - **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per 1M tokens + - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal, \$1.25/\$10 per 1M tokens + - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per 1M tokens - **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed - **Cons:** Requires internet connection, ongoing costs, data leaves your machine @@ -349,7 +349,7 @@ Before jumping into code generation, let's set up the "invisible infrastructure" AI Rules (also called Cursor Rules, or system prompts) are instructions that automatically precede every conversation with your AI assistant. They're like permanent coaching that guides the AI's behavior. -**AGENTS.md: The Emerging Standard** +:::{dropdown} **AGENTS.md: The Emerging Standard** In 2025, the AI coding ecosystem converged on [**AGENTS.md**](https://agents.md/) as the universal format for agent instructions. Emerging as an open standard with OpenAI convening an industry working group and growing adoption across the ecosystem, AGENTS.md replaces fragmented tool-specific formatsโ€”it's just plain Markdown, no special schemas needed. @@ -366,8 +366,7 @@ In 2025, the AI coding ecosystem converged on [**AGENTS.md**](https://agents.md/ | **Aider** | โš™๏ธ Config | Add to .aider.conf.yml: `read: AGENTS.md` | | **Continue.dev** | โŒ Not yet | Use .continue/rules/ | | **Cline** | โŒ Not yet | Use .clinerules/rules.md | - -**Key advantage:** Single AGENTS.md file works across your entire team regardless of which AI tool they useโ€”no more maintaining separate .cursorrules, CLAUDE.md, and GEMINI.md files. +::: For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. @@ -400,10 +399,6 @@ cursor . 5. **Check that the rules file exists:** Look for `AGENTS.md` file in your extension root - :::{important} - If neither `AGENTS.md` exists, **let an instructor know!** These files contain important context and rules that help AI assistants generate better JupyterLab extension code. - ::: - 6. **Review the ruleset file** Open `AGENTS.md` Key sections you'll find: @@ -516,8 +511,7 @@ For planning and architecture, always use the highest-quality model available: You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM-4.5 Air) for routine edits, but don't skimp on the thinking phase. ::: -2. Ask AI chat questions to verify that it recognizes the rules. Paste the below prompt into the chat - +2. Paste the following prompt into a chat to verify that Cursor is using our rules: ``` What package manager should I use for JupyterLab extension frontend? ``` @@ -535,7 +529,7 @@ You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM - Check that the file is named exactly `AGENTS.md` (case-sensitive) ::: -3. Get ready for the development. Start a new chat and choose Agent Mode: +3. Get ready for development. Start a new chat choose Agent Mode and send this prompt: ``` Prepare my extension for development: 1. Check that I am using the correct environment `jupytercon2025` From f93cd3162c059f048242d7597c1dabbdbc9f8989 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 07:08:37 -0700 Subject: [PATCH 089/118] remove recommendation for option 2 --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 4a4731bb..ad5b8aa5 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -258,7 +258,7 @@ If you completed the anatomy module and want to continue with your extension: 4. Skip to [AI tool](#ai-tool) below. -#### ๐Ÿ“ฅ Option 2: Fork the finished extension (recommended) +#### ๐Ÿ“ฅ Option 2: Fork the finished extension If you'd prefer to start fresh or didn't complete the anatomy module: From 768eb51562ebe9636bba9482b3396dfffbd1e98e Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 07:20:05 -0700 Subject: [PATCH 090/118] more cleanup --- 04-materials/05-developing-with-ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index ad5b8aa5..a86db2bf 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -408,7 +408,7 @@ cursor . - When to use `ReactWidget` vs `Widget` - REST with `ServerConnection`, state with `IStateDB` - **Development Workflow (must-know):** + **Development Workflow:** - Run `jlpm build` for TS changes; restart Jupyter for Python changes - Debug via browser console and server logs @@ -684,7 +684,7 @@ To undo all changes made by the one-shot prompt: git restore . # Remove any new untracked files created by the AI -git clean -fd +git clean -Xdf ``` ## ๐Ÿ“Š Exercise C (20 minutes): Product manager framework From ed9a000e9b314e3fd3eb923aec1225007f541c57 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 07:21:16 -0700 Subject: [PATCH 091/118] add pip install for rebuilding just in case --- 04-materials/05-developing-with-ai.md | 1 + 1 file changed, 1 insertion(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index a86db2bf..bccc0bb6 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -629,6 +629,7 @@ Send the prompt and watch as it generates the entire feature. **In about 2-3 min **Test the functionality:** ```bash jlpm build +pip intstall -e . jupyter lab ``` From e11e57535555f9ce7e761735ff7f288651c9a635 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 08:10:36 -0700 Subject: [PATCH 092/118] close hanging {tip} myst tag --- 04-materials/05-developing-with-ai.md | 1 + 1 file changed, 1 insertion(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index bccc0bb6..ff5ff432 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -789,6 +789,7 @@ Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-ba - โœ… Persistent across sessions Always save plans to files: `plans/*.md` +::: ### Implement Phase by Phase From 6bfc668e3468b47230647d5ab04915ab2180943c Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 08:12:26 -0700 Subject: [PATCH 093/118] fix new cgat shortcut --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index ff5ff432..d9b8f872 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -814,7 +814,7 @@ As you work through phases, keep an eye on these metrics: - โœ… Major refactoring complete ::: -1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + K`) +1. **Start a NEW chat** for Phase 1 (`Cmd/Ctrl + L` to focus on chat panel, then `Cmd/Ctrl + N` to start a new chat) 2. **Reference the plan:** From 517110965f2726d3121e100655f396117beceffe Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 08:23:27 -0700 Subject: [PATCH 094/118] polish tip on context monitoring --- 04-materials/05-developing-with-ai.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index d9b8f872..36ed0d6e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -798,9 +798,10 @@ Instead of one long chat for everything, start fresh for each phase. Why new chats? It has do with LLMs context window. Saturating the context window leads to AI confusion and runs up the costs. File-based plans allow us to start new chats and still keep the required context. -As you work through phases, keep an eye on these metrics: +As you work through phases, keep an eye on **context window percentage** (shown in Cursor chat): + +[![Screenshot of Cursor's context monitor showing "16.3% โ€ข 32.5K / 200K context used" in the top toolbar](../assets/images/context-monitor.png) -**Context window percentage** (shown in Cursor chat): - **< 30%:** Healthy, plenty of room - **30-50%:** Good, AI still focused - **50-70%:** Getting crowded, consider new chat soon From df610c65c0fb179d6b8914c1fe1e3b28558c7d96 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 08:24:28 -0700 Subject: [PATCH 095/118] forgot ot add the image --- assets/images/context-monitor.png | Bin 0 -> 27871 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/context-monitor.png diff --git a/assets/images/context-monitor.png b/assets/images/context-monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..f7cc6e1b1a7e4d20e05145ffa59c5676a976bd64 GIT binary patch literal 27871 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 B9q9l7 literal 0 HcmV?d00001 From 12e5f4d4e9c2524a73864bd04c68f284c0c8ae79 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 08:26:09 -0700 Subject: [PATCH 096/118] remove erroneoues extra `[` --- 04-materials/05-developing-with-ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 36ed0d6e..0f6558d9 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -518,7 +518,7 @@ You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM :::{note} No Visual Indicator Cursor automatically reads and applies AGENTS.md, but there's **no visual indicator** in the interface showing it's active. - [![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) + ![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) ::: AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! @@ -800,7 +800,7 @@ Why new chats? It has do with LLMs context window. Saturating the context window As you work through phases, keep an eye on **context window percentage** (shown in Cursor chat): -[![Screenshot of Cursor's context monitor showing "16.3% โ€ข 32.5K / 200K context used" in the top toolbar](../assets/images/context-monitor.png) +![Screenshot of Cursor's context monitor showing "16.3% โ€ข 32.5K / 200K context used" in the top toolbar](../assets/images/context-monitor.png) - **< 30%:** Healthy, plenty of room - **30-50%:** Good, AI still focused From c191d548a7d30b8190a3d62ef803e9c0a97012ec Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 10:40:34 -0700 Subject: [PATCH 097/118] add phase 3 and follow up --- 04-materials/05-developing-with-ai.md | 81 +++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 0f6558d9..e57a8ad0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -845,6 +845,14 @@ As you work through phases, keep an eye on **context window percentage** (shown Phase 1 is complete. Now implement advanced filters (blur, sharpen, crop). ``` + :::{tip} Follow up and refine after reviewing the changes. + + For example, I noticed that Rest button disappeared from the UI + ``` + Where did the reset button go? + ``` + ::: + 6. **Commit after Phase 2 works:** ```bash @@ -852,15 +860,80 @@ As you work through phases, keep an eye on **context window percentage** (shown git commit -m "Phase 2: Add advanced filters (blur, sharpen, crop)" ``` -::: Adopt a "product manager" mindset +7. **Start ANOTHER fresh chat for Phase 3:** + + ``` + We are ready for Phase 3 of @plans/image-editing-feature.md + + Phase 2 is complete. Now implement the polish features: + - Save edited image functionality + - Undo/redo buttons + - Loading states and error handling + ``` + +8. Add additional features that might be needed + + Now that Phase 3 is complete (with undo/redo, save, and history), consider adding a feature like **Custom Filter Presets**. + + :::{tip} **Structure Prompts as User Stories** + + **Effective prompts follow the user story format: clear requirements, constraints, and acceptance criteria.** + + Instead of: + > "Let users save their favorite filter combinations" + + Try this structure: + + ````markdown + **User Story:** As a user who frequently applies the same combination of filters, I want to save my favorite filter sequences as named presets and quickly reapply them to new images, so I can maintain consistent editing styles without manually repeating steps. + + **Acceptance Criteria:** + - [ ] Users can save the current filter sequence as a named preset (e.g., "Vintage Look") + - [ ] A preset dropdown menu displays all saved presets + - [ ] Clicking a preset applies all its filters in sequence to the current image + - [ ] Users can delete presets they no longer need + - [ ] Presets persist across JupyterLab sessions + - [ ] A tooltip shows which filters are included in each preset when hovering + - [ ] Maximum of 10 presets can be saved (to prevent cluttering the UI) + - [ ] Preset names must be unique and non-empty + + **Technical Requirements:** + - Backend: Add `/api//presets` endpoints (GET, POST, DELETE) + - Storage: Store user-created presets in a JSON file at `~/.jupytercon-image-editor/settings.json` + - Use Python's `pathlib.Path` to handle cross-platform paths + - Create the directory if it doesn't exist (with appropriate permissions) + - Use `json.load()` and `json.dump()` for reading/writing + - Handle file locking for concurrent access (if needed) + - Data model: `{id: string, name: string, filters: Array<{type: string, params: object}>, created: timestamp}` + - Frontend: Add "Save as Preset" button and preset dropdown to toolbar + - UI: Use `@jupyterlab/apputils` `showDialog` for naming new presets + - Validation: Check for name uniqueness and length (3-30 characters) + - Apply preset: Reuse existing filter application logic from Phase 2 + + **Non-Requirements (for later):** + - Don't implement preset sharing/export yet + - Don't support editing existing presets (delete and recreate is fine for now) + - Don't add preset thumbnails/previews + - Don't implement preset categories or folders + - Performance optimization for applying complex presets can wait + + **Questions for AI:** + - How should we handle concurrent writes if multiple JupyterLab instances are running? + - What's the best error handling if the settings file is corrupted or unreadable? + - Should we create a backup of the settings file before writing changes? + - What's the best UX for the preset dropdown - regular select, menu bar item, or palette command? + - How do we handle if a preset contains filters with deprecated parameters in future versions? + ```` + + This level of detail helps AI give you exactly what you want. + ::: -:::{important} ๐Ÿ’พ **Final Git commit and push!** +9. ๐Ÿ’พ **Final Git commit and push!** ```bash git add . -git commit -m "Complete Exercise 1: Image editing with AI assistance" +git commit -m "Complete image editor feature" git push ``` -::: ## ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) From 7afc97402e56fc19863448535ebd6feba9d69b39 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 10:57:07 -0700 Subject: [PATCH 098/118] wrap up exercise C --- 04-materials/05-developing-with-ai.md | 150 ++++++++++++++++---------- 1 file changed, 94 insertions(+), 56 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index e57a8ad0..73d7f94e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -871,64 +871,102 @@ As you work through phases, keep an eye on **context window percentage** (shown - Loading states and error handling ``` -8. Add additional features that might be needed - - Now that Phase 3 is complete (with undo/redo, save, and history), consider adding a feature like **Custom Filter Presets**. - - :::{tip} **Structure Prompts as User Stories** - - **Effective prompts follow the user story format: clear requirements, constraints, and acceptance criteria.** - - Instead of: - > "Let users save their favorite filter combinations" - - Try this structure: - - ````markdown - **User Story:** As a user who frequently applies the same combination of filters, I want to save my favorite filter sequences as named presets and quickly reapply them to new images, so I can maintain consistent editing styles without manually repeating steps. - - **Acceptance Criteria:** - - [ ] Users can save the current filter sequence as a named preset (e.g., "Vintage Look") - - [ ] A preset dropdown menu displays all saved presets - - [ ] Clicking a preset applies all its filters in sequence to the current image - - [ ] Users can delete presets they no longer need - - [ ] Presets persist across JupyterLab sessions - - [ ] A tooltip shows which filters are included in each preset when hovering - - [ ] Maximum of 10 presets can be saved (to prevent cluttering the UI) - - [ ] Preset names must be unique and non-empty - - **Technical Requirements:** - - Backend: Add `/api//presets` endpoints (GET, POST, DELETE) - - Storage: Store user-created presets in a JSON file at `~/.jupytercon-image-editor/settings.json` - - Use Python's `pathlib.Path` to handle cross-platform paths - - Create the directory if it doesn't exist (with appropriate permissions) - - Use `json.load()` and `json.dump()` for reading/writing - - Handle file locking for concurrent access (if needed) - - Data model: `{id: string, name: string, filters: Array<{type: string, params: object}>, created: timestamp}` - - Frontend: Add "Save as Preset" button and preset dropdown to toolbar - - UI: Use `@jupyterlab/apputils` `showDialog` for naming new presets - - Validation: Check for name uniqueness and length (3-30 characters) - - Apply preset: Reuse existing filter application logic from Phase 2 - - **Non-Requirements (for later):** - - Don't implement preset sharing/export yet - - Don't support editing existing presets (delete and recreate is fine for now) - - Don't add preset thumbnails/previews - - Don't implement preset categories or folders - - Performance optimization for applying complex presets can wait - - **Questions for AI:** - - How should we handle concurrent writes if multiple JupyterLab instances are running? - - What's the best error handling if the settings file is corrupted or unreadable? - - Should we create a backup of the settings file before writing changes? - - What's the best UX for the preset dropdown - regular select, menu bar item, or palette command? - - How do we handle if a preset contains filters with deprecated parameters in future versions? - ```` +### Prompts as user stories + +Now that Phase 3 is complete (with undo/redo, save, and history), consider adding a feature like **Custom Filter Presets**. + +:::{tip} **Structure Prompts as User Stories** + +**Effective prompts follow the user story format: clear requirements, constraints, and acceptance criteria.** + +Instead of: +> "Let users save their favorite filter combinations" + +Try this structure: + +````markdown +**User Story:** As a user who frequently applies the same combination of filters, I want to save my favorite filter sequences as named presets and quickly reapply them to new images, so I can maintain consistent editing styles without manually repeating steps. + +**Acceptance Criteria:** +- [ ] Users can save the current filter sequence as a named preset (e.g., "Vintage Look" = sepia + slight blur) +- [ ] A preset dropdown menu displays all saved presets +- [ ] Clicking a preset applies all its filters in sequence to the current image +- [ ] Users can delete presets they no longer need +- [ ] Presets persist across JupyterLab sessions +- [ ] A tooltip shows which filters are included in each preset when hovering +- [ ] Maximum of 10 presets can be saved (to prevent cluttering the UI) +- [ ] Preset names must be unique and non-empty + +**Technical Requirements:** +- Backend: Add `/api//presets` endpoints (GET, POST, DELETE) +- Storage: Store user-created presets in a JSON file at `~/.jupytercon-image-editor/settings.json` + - Use Python's `pathlib.Path` to handle cross-platform paths + - Create the directory if it doesn't exist (with appropriate permissions) + - Use `json.load()` and `json.dump()` for reading/writing + - Handle file locking for concurrent access (if needed) +- Data model: `{id: string, name: string, filters: Array<{type: string, params: object}>, created: timestamp}` +- Frontend: Add "Save as Preset" button and preset dropdown to toolbar +- UI: Use `@jupyterlab/apputils` `showDialog` for naming new presets +- Validation: Check for name uniqueness and length (3-30 characters) +- Apply preset: Reuse existing filter application logic from Phase 2 + +**Non-Requirements (for later):** +- Don't implement preset sharing/export yet +- Don't support editing existing presets (delete and recreate is fine for now) +- Don't add preset thumbnails/previews +- Don't implement preset categories or folders +- Performance optimization for applying complex presets can wait + +**Questions for AI:** +- How should we handle concurrent writes if multiple JupyterLab instances are running? +- What's the best error handling if the settings file is corrupted or unreadable? +- Should we create a backup of the settings file before writing changes? +- What's the best UX for the preset dropdown - regular select, menu bar item, or palette command? +- How do we handle if a preset contains filters with deprecated parameters in future versions? +```` + +This level of detail helps AI give you exactly what you want. +::: - This level of detail helps AI give you exactly what you want. - ::: +:::{dropdown} Want to continue exploring? + +If you finish early or want to continue exploring, try implementing more features: + +- **Selective crop tool:** Replace the basic center crop with an interactive selector tool that lets users drag to define the crop area +- **Image rotation:** Add 90-degree rotation buttons (clockwise and counter-clockwise) +- **Filter preview:** Show a small preview thumbnail for each filter before applying it +- **Keyboard shortcuts:** Add keyboard shortcuts for common filters (g for grayscale, s for sepia) +- **Before/After comparison:** Add a split-screen or toggle button to compare the original image with the edited version +::: -9. ๐Ÿ’พ **Final Git commit and push!** +### Wrap up + +### Key Takeaways + +โœ… **AI excels at:** +- **Scaffolding and boilerplate** - New endpoints, UI components, tests +- **Navigating unfamiliar APIs** - Pillow, new JupyterLab features +- **Systematic tasks** - Following patterns, applying transforms +- **Explanation and education** - "Why did you choose this approach?" +- **Self-correction** - Fixing build errors, addressing type issues +- **Iterative refinement** - Adjusting based on your feedback + +โš ๏ธ **AI may struggle with:** +- **Complex architectural decisions** - When to use State DB vs. props +- **Complex async bugs** - Race conditions, timing issues, subtle promise chaining errors +- **Performance optimization** - Knowing when code is "fast enough" +- **Project-specific conventions** - Without AGENTS.md guidance +- **Ambiguous requirements** - "Make it better" vs. specific criteria + +๐ŸŽฏ **The sweet spot:** +AI is most effective when you provide: +1. **Clear requirements** (Product Manager mindset) +2. **Project context** (AGENTS.md rules, documentation) +3. **Phased plans** (not trying to do everything at once) +4. **Iterative feedback** (junior developer coaching) +5. **Safety nets** (Git commits, testing) + +๐Ÿ’พ **Final Git commit and push!** ```bash git add . git commit -m "Complete image editor feature" From 3a2920bf9f85a8472255da881e5aa027353d3d13 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 10:58:34 -0700 Subject: [PATCH 099/118] consistent prompt formatting --- 04-materials/05-developing-with-ai.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 73d7f94e..a3b7f8fc 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -880,7 +880,9 @@ Now that Phase 3 is complete (with undo/redo, save, and history), consider addin **Effective prompts follow the user story format: clear requirements, constraints, and acceptance criteria.** Instead of: -> "Let users save their favorite filter combinations" +````markdown +Let users save their favorite filter combinations +```` Try this structure: From b34a30762a62306ec444f07b9cd87757a0c23623 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 11:00:47 -0700 Subject: [PATCH 100/118] consistent sentence case --- 04-materials/05-developing-with-ai.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index a3b7f8fc..1a8d128b 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -370,7 +370,7 @@ In 2025, the AI coding ecosystem converged on [**AGENTS.md**](https://agents.md/ For this workshop, the official copier template provides AGENTS.md and can create symlinks for Claude Code and Gemini CLI. You should already have these rules configured in your repo if you selected 'Y' on the copier's question about AI tools. Let's understand what's there and why it helps. -### What's in Your AGENTS.md File +### What's in your AGENTS.md file :::{tip} The Magic Behind Good AI Code When you see AI generate well-structured JupyterLab code in exercises, it's not magic - it's reading your AGENTS.md file! These rules are why AI knows to: @@ -601,7 +601,7 @@ Technical requirements: - Maintain the existing refresh functionality ``` -### What Happens with This Prompt? +### What happens with this prompt? When you give this prompt to an AI agent like Cursor or Claude Code, it will typically: @@ -640,7 +640,7 @@ jupyter lab - Check the terminal running `jupyter lab` for Python errors - Find at least 3 decisions you might have made differently -### The Hidden Cost: Decisions Made Without You +### The hidden cost: Decisions made without you While impressive, this one-shot approach makes numerous decisions on your behalf: @@ -706,7 +706,7 @@ This takes longer but results in: - โœ… Proper error handling and edge cases - โœ… Learning opportunities at each step -### The rise of the Product Manager mindset +### The rise of the product manager mindset AI works best with detailed specifications, not agile "figure it out as we go." Embrace structured planning. Before generating any code, we'll have AI create a phased implementation plan. This: @@ -791,7 +791,7 @@ Cursor has a "Plan" mode that creates temporary plans. **Don't use it.** File-ba Always save plans to files: `plans/*.md` ::: -### Implement Phase by Phase +### Implement phase by phase :::{tip} Managing Context and Costs Instead of one long chat for everything, start fresh for each phase. @@ -943,7 +943,7 @@ If you finish early or want to continue exploring, try implementing more feature ### Wrap up -### Key Takeaways +### Key takeaways โœ… **AI excels at:** - **Scaffolding and boilerplate** - New endpoints, UI components, tests @@ -1001,7 +1001,7 @@ git push - Type `y` to accept, `n` to skip, or `e` to edit - Changes are applied directly to your files -### Claude Code Tips +### Claude Code tips **Run commands without leaving the chat:** From 025d61a6b0c4f520d6f12d1137aab341837d1ca2 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 11:18:05 -0700 Subject: [PATCH 101/118] add git safety net explanations --- 04-materials/05-developing-with-ai.md | 137 +++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 11 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 1a8d128b..cdfaf0a3 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -296,6 +296,31 @@ If you'd prefer to start fresh or didn't complete the anatomy module: Make sure your git tree is clean, there are no unsaved and uncommitted files. This is going to be important later +### ๐Ÿ›ก๏ธ Set up your safety net: Git workflow + +Before diving into AI-assisted development, establish a safety workflow. AI can generate code that breaks your extension, so you need the ability to roll back instantly. + +:::{danger} Git is Your Safety Net +AI can suggest code that breaks your extension. With frequent commits and staging, you can fearlessly experiment and roll back instantly. **This is not optional**โ€”it's how you work safely with AI. +::: + +**The Four Safety Levels:** + +``` +Level 1: Unsaved โ†’ Files on disk (Cmd/Ctrl + Z to undo) +Level 2: Staged โ†’ git add (can unstage) +Level 3: Committed โ†’ git commit (can reset) +Level 4: Pushed โ†’ git push (permanent) +``` + +**Keep an eye on Source Control** +- Open the Source Control view (`Ctrl + Shift + G`) +- Keep this panel visible alongside your AI chat +- You'll review all AI-generated changes here before committing + + +We'll cover the detailed git workflow when you start generating code in Exercise B. + (ai-tool)= ### โš™๏ธ AI tool @@ -556,6 +581,48 @@ Once AI can reference your project structure, coding rules, and build your proje ## ๐Ÿ—๏ธ Exercise B (30 minutes): Build it! + +### ๐Ÿ”„ Your git workflow for AI-generated code + +Now that you're about to generate substantial code with AI, let's establish a disciplined workflow for reviewing and staging changes. + +**Adopt this workflow:** + +```bash +# After AI generates code: +# 1. Review changes in Source Control panel (Cmd/Ctrl + Shift + G) + +# 2. Test if it works - build and verify +jlpm build +jupyter lab # Test the feature + +# 3. Stage changes you like (selectively): +git add src/widget.ts # stage individual files +git add jupytercon2025_extension_workshop/routes.py + +# 4. If AI continues and breaks something: +git restore src/widget.ts # revert to last staged/committed version + +# 5. Once everything works and is staged: +git commit -m "Add image filter buttons with AI assistance" + +# 6. If you need to undo a commit (but keep the changes): +git reset --soft HEAD~1 # undo commit, keep changes staged + +# 7. If you need to undo a commit AND the changes: +git reset --hard HEAD~1 # โš ๏ธ destructive - use carefully +``` + +:::{tip} Stage Early, Stage Often +When AI generates code that works, immediately stage those files (`git add`). This creates a safety checkpoint. If AI's next changes break things, you can quickly `git restore` back to the working state without losing everything. +::: + +**Keep Source Control panel visible:** +- `Ctrl + Shift + G` to open +- Shows all modified files with diff preview +- Click any file to see exactly what changed +- Stage/unstage with + and - buttons + ### Understanding your starting point Before we extend the functionality, a quick reminder on what the extension currently does: @@ -678,16 +745,37 @@ This works great for prototypes, but in production code, you need to understand ### Roll back when done -To undo all changes made by the one-shot prompt: +This is where your git safety net proves its worth! The one-shot prompt likely generated 200+ lines across multiple files. Let's practice using the **Four Safety Levels** to safely undo everything. + +**To completely undo all changes made by the one-shot prompt:** ```bash -# Discard all changes to tracked files +# Level 2 โ†’ Level 1: Discard all changes to tracked files git restore . -# Remove any new untracked files created by the AI -git clean -Xdf +# Clean up any new untracked files created by AI +# (like new dependencies or generated files) +git clean -fd # removes untracked files +git clean -Xdf # also removes files ignored by .gitignore +``` + +:::{tip} Why This Exercise Matters +You just learned to fearlessly experiment with AI: +1. โœ… Let AI generate a complete feature in minutes +2. โœ… Review and test the implementation +3. โœ… Roll back completely to try a different approach +4. โœ… No permanent damage, clean slate to start structured development + +This is the **safety net in action**. With git, you can take risks with AI and always recover. +::: + +**Verify clean state:** +```bash +git status # Should show "nothing to commit, working tree clean" ``` +Now you're ready to proceed with the structured, phased approach in Exercise C. + ## ๐Ÿ“Š Exercise C (20 minutes): Product manager framework ### The better way: structured, iterative development @@ -829,15 +917,37 @@ As you work through phases, keep an eye on **context window percentage** (shown Note the `@plans/...` syntax tells AI to read that specific file. 3. **Review changes in Source Control** (keep this panel open!) + - Open `Ctrl + Shift + G` to see all modified files + - Click each file to review the diff + - Look for unexpected changes or files you didn't anticipate -4. **Commit after Phase 1 works:** +4. **Test the implementation:** + ```bash + jlpm build + jupyter lab + ``` + - Try the new filter buttons + - Check browser console (`F12`) for errors + - Verify backend logs in terminal + +5. **Stage and commit after Phase 1 works:** ```bash - git add . + # Stage only the files you've reviewed and approved + git add src/widget.ts + git add jupytercon2025_extension_workshop/routes.py + git add pyproject.toml + # and more if needed + + # Commit with a descriptive message git commit -m "Phase 1: Add basic image filters (grayscale, sepia)" ``` -5. **Start ANOTHER fresh chat for Phase 2:** + :::{tip} Selective Staging + Don't blindly `git add .` โ€” review each file first. AI might have modified files you didn't expect or left debugging code. Selective staging gives you control. + ::: + +6. **Start ANOTHER fresh chat for Phase 2:** ``` We are ready for Phase 2 of @plans/image-editing-feature.md @@ -847,20 +957,25 @@ As you work through phases, keep an eye on **context window percentage** (shown :::{tip} Follow up and refine after reviewing the changes. - For example, I noticed that Rest button disappeared from the UI + For example, I noticed that Reset button disappeared from the UI ``` Where did the reset button go? ``` ::: -6. **Commit after Phase 2 works:** +7. **Review, test, and commit after Phase 2 works:** ```bash + # Review in Source Control panel, test the features + jlpm build + jupyter lab + + # Stage and commit git add . git commit -m "Phase 2: Add advanced filters (blur, sharpen, crop)" ``` -7. **Start ANOTHER fresh chat for Phase 3:** +8. **Start ANOTHER fresh chat for Phase 3:** ``` We are ready for Phase 3 of @plans/image-editing-feature.md @@ -966,7 +1081,7 @@ AI is most effective when you provide: 2. **Project context** (AGENTS.md rules, documentation) 3. **Phased plans** (not trying to do everything at once) 4. **Iterative feedback** (junior developer coaching) -5. **Safety nets** (Git commits, testing) +5. **Safety nets** (Git commits at each checkpoint, testing) ๐Ÿ’พ **Final Git commit and push!** ```bash From 92a94ed8f175ea8e9aae2965a16e76e8172a71a8 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 11:39:21 -0700 Subject: [PATCH 102/118] add visual debugging --- 04-materials/05-developing-with-ai.md | 45 +++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index cdfaf0a3..b0c9992d 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -739,9 +739,50 @@ When you use one-shot prompts, you're essentially saying: "AI, you be the produc This works great for prototypes, but in production code, you need to understand and own these decisions. ::: -### Basic debugging +### Visual debugging with screenshots -**The debugging workflow:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. +AI can understand what your extension looks like! This is powerful for debugging UI issues or requesting design changes. + +**Try it now:** + +1. **Open your extension in JupyterLab** (should still be running from earlier) + +2. **Take a screenshot of the extension widget:** + - **macOS:** Press `Cmd + Shift + 4`, then drag to select the widget area + - **Windows:** Use Snipping Tool or `Win + Shift + S` + - **Linux:** Use your screenshot tool (varies by desktop environment) + +3. **Open Cursor chat** (`Cmd/Ctrl + L`) and drag or paste the screenshot into the chat + +4. **Try one of these prompts with your screenshot:** + + ``` + [Drop screenshot here] + + Please adjust the filter button spacing: + - Add 8px margin between buttons + - Increase padding inside each button to match JupyterLab's standard button styling + ``` + +**When to use screenshots:** +- โœ… Layout and spacing problems ("buttons are misaligned") +- โœ… Color and theming issues ("doesn't match JupyterLab theme") +- โœ… Showing desired design ("make it look like this") +- โœ… Component placement ("move this above that") + +:::{tip} Visual Feedback Loop +Screenshots create a remarkably effective feedback loop: +1. Implement feature +2. Screenshot the result +3. Show AI: "The buttons should be above the image" +4. AI adjusts layout +5. Reload, take new screenshot +6. Show AI: "Perfect, but reduce spacing by half" + +This is often faster than describing layout issues in words! +::: + +**The debugging workflow for errors:** Don't manually debugโ€”let AI help! It can read error messages, understand context, and propose fixes. If you encounter TypeScript compilation errors or Python exceptions, copy the error message into chat and ask AI to fix it. ### Roll back when done From c8984975007d17853bda29b3d11259aafe733acc Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 11:52:07 -0700 Subject: [PATCH 103/118] small fixes --- 04-materials/05-developing-with-ai.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index b0c9992d..85648a32 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -199,7 +199,9 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - **LLM Options:** - Built-in models (Claude Sonnet 4.5, GPT-5, Gemini 2.5 Pro and more) with Cursor subscription - **Best for:** Developers who want a polished, GUI-driven experience -- **Alternatives:** [Windsurf](https://codeium.com/windsurf) (free tier, \$15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) (\$10-39/mo), [Cline](https://cline.bot/) (VS Code extension, free), [Continue](https://continue.dev/) (VS Code/JetBrains extension, free or \$10/mo Teams), [Roo Code](https://roocode.com/) (VS Code extension, free or \$20/mo Pro), [Kilocode](https://kilocode.ai/) (VS Code/JetBrains, free or \$29/user/mo Teams), [Replit Agent](https://replit.com/) (cloud-based) +::::{dropdown} Alternatives +- [Windsurf](https://codeium.com/windsurf) (free tier, \$15/mo Pro), [GitHub Copilot Workspace](https://github.com/features/copilot) (\$10-39/mo), [Cline](https://cline.bot/) (VS Code extension, free), [Continue](https://continue.dev/) (VS Code/JetBrains extension, free or \$10/mo Teams), [Roo Code](https://roocode.com/) (VS Code extension, free or \$20/mo Pro), [Kilocode](https://kilocode.ai/) (VS Code/JetBrains, free or \$29/user/mo Teams), [Replit Agent](https://replit.com/) (cloud-based) +:::: - **Download:** [cursor.com](https://cursor.com/) #### 2. ๐Ÿ’ป **Claude Code** @@ -208,7 +210,9 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat - Requires Claude subscription or Anthropic API key. Can also work through cloud providers, like Amazon Bedrock - Works with Opus 4.1, Sonnet 4.5, Haiku 4.5, and other Claude models - **Best for:** CLI warriors who live in the terminal -- **Alternatives:** [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) +::::{dropdown} Alternatives +- [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) +:::: - **Install:** Requires Node.js, then `npm install --global @anthropic/claude-code` :::{note} Further Reading @@ -324,9 +328,7 @@ We'll cover the detailed git workflow when you start generating code in Exercise (ai-tool)= ### โš™๏ธ AI tool -We will be using Cursor and Claude Code throughout this tutorial. Please install them if you would like to follow along. - -You are totally welcome to use any AI tool you have installed on your computer! Many of them follow similar patterns and expose similar functionality. +We will be using Cursor and Claude Code throughout this tutorial. Please install them if you would like to follow along. Other tools work similarly, but we wonโ€™t cover them here. #### ๐ŸŽจ Setting up Cursor @@ -359,7 +361,11 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on ``` 3. **Set up Claude subscription or Anthropic API key** - - More details to be added + - We will be using Claude models provided by AWS Bedrock in this tutorial: + ```bash + # macOS/Linux + export AWS_BEARER_TOKEN_BEDROCK=your-bedrock-api-key + ``` By now, you should have: - โœ… Cursor installed with a free account created @@ -543,7 +549,7 @@ You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM :::{note} No Visual Indicator Cursor automatically reads and applies AGENTS.md, but there's **no visual indicator** in the interface showing it's active. - ![The Cursor interface showing the user promopt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) + ![The Cursor interface showing the user prompt "Can I use package-lock.json with this repo?" with a part of the AI model "thinking" tokens saying "From the rules section, there's a clear directive about package management: โœ… Do: Use jlpm exclusively"](../assets/images/implicit-agents-md.png) ::: AI should respond with `jlpm`, not `npm` or `yarn` - that comes from your AGENTS.md rules! @@ -566,7 +572,7 @@ You can downgrade to faster/cheaper models (Claude Haiku 4.5, GPT-5 Mini, or GLM AI should respond with (Claude Sonnet 4.5): - Checking your environment and switching to `jupytercon2025` for the rest of the commands - - Verify tools like `jlpm` are available in the environment` + - Verify tools like `jlpm` are available in the environment - Checking that extension is currently installed by looking into the outputs of `jupyter labextension list` and `jupyter server extension list` - Building the extension for you - Providing a summary of operations and suggestions on how to get it running @@ -696,7 +702,7 @@ Send the prompt and watch as it generates the entire feature. **In about 2-3 min **Test the functionality:** ```bash jlpm build -pip intstall -e . +pip install -e . jupyter lab ``` @@ -925,7 +931,7 @@ Always save plans to files: `plans/*.md` :::{tip} Managing Context and Costs Instead of one long chat for everything, start fresh for each phase. -Why new chats? It has do with LLMs context window. Saturating the context window leads to AI confusion and runs up the costs. File-based plans allow us to start new chats and still keep the required context. +Why new chats? It has to do with LLMs context window. Saturating the context window leads to AI confusion and runs up the costs. File-based plans allow us to start new chats and still keep the required context. As you work through phases, keep an eye on **context window percentage** (shown in Cursor chat): From d94ba75f70c54859520fe7523a28780e56bbf297 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 11:55:36 -0700 Subject: [PATCH 104/118] small fixes + emojis --- 04-materials/05-developing-with-ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 85648a32..90a0703f 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -213,7 +213,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat ::::{dropdown} Alternatives - [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) :::: -- **Install:** Requires Node.js, then `npm install --global @anthropic/claude-code` +- **Install:** Requires Node.js, then `npm install --global @anthropic-ai/claude-code` :::{note} Further Reading :class: dropdown @@ -1137,7 +1137,7 @@ git commit -m "Complete image editor feature" git push ``` -## ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) +## ๐Ÿ–ฅ๏ธ Demo: AI from the command line (10 minutes) 1. **Start an interactive session**: From 42874ae8589030b1f3ed29c0582932290583a96c Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:41:34 -0700 Subject: [PATCH 105/118] add more detailed claude code instructions --- 04-materials/05-developing-with-ai.md | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 90a0703f..6a3ab370 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -360,11 +360,44 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on npm install --global @anthropic-ai/claude-code ``` -3. **Set up Claude subscription or Anthropic API key** - - We will be using Claude models provided by AWS Bedrock in this tutorial: +3. **Set up AWS Bedrock authentication** + + We will be using Claude models provided by AWS Bedrock in this tutorial. + + **Required environment variables:** + ```bash # macOS/Linux export AWS_BEARER_TOKEN_BEDROCK=your-bedrock-api-key + export CLAUDE_CODE_USE_BEDROCK=1 + export AWS_REGION=us-east-1 # or your region + ``` + + :::{note} **About Bedrock API Keys** + - Bedrock API keys provide simpler authentication without needing full AWS credentials + - Learn more about [Bedrock API keys](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-use.html) + - Get your API key from the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) + ::: + + :::{dropdown} Additional customization (optional) + **To customize models:** + + ```bash + export ANTHROPIC_MODEL='global.anthropic.claude-sonnet-4-5-20250929-v1:0' + export ANTHROPIC_SMALL_FAST_MODEL='us.anthropic.claude-haiku-4-5-20251001-v1:0' + ``` + + **Recommended token settings for Bedrock:** + + ```bash + export CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096 + export MAX_THINKING_TOKENS=1024 + ``` + + **Why these token settings?** + - `CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096`: Bedrock's throttling sets a minimum 4096 token penalty. Setting lower won't reduce costs but may cut off responses. + - `MAX_THINKING_TOKENS=1024`: Provides space for extended thinking without cutting off tool use responses. + ::: ``` By now, you should have: From 91ecdc26587c8d026055c9d59fdaca24480d7c73 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:44:59 -0700 Subject: [PATCH 106/118] fix git add and commit messages --- 04-materials/05-developing-with-ai.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 6a3ab370..2c8de9cd 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -1050,8 +1050,10 @@ As you work through phases, keep an eye on **context window percentage** (shown jlpm build jupyter lab - # Stage and commit - git add . + # Stage and commit (review each file first!) + git add src/widget.ts + git add jupytercon2025_extension_workshop/routes.py + # add any other modified files git commit -m "Phase 2: Add advanced filters (blur, sharpen, crop)" ``` @@ -1066,6 +1068,22 @@ As you work through phases, keep an eye on **context window percentage** (shown - Loading states and error handling ``` +9. **Review, test, and commit after Phase 3 works:** + + ```bash + # Review in Source Control panel, test the features + jlpm build + jupyter lab + + # Stage and commit + git add src/widget.ts + git add src/api.ts + git add jupytercon2025_extension_workshop/routes.py + git add jupytercon2025_extension_workshop/image_processing.py + # add any other modified files + git commit -m "Phase 3: Add save, undo/redo, and error handling" + ``` + ### Prompts as user stories Now that Phase 3 is complete (with undo/redo, save, and history), consider adding a feature like **Custom Filter Presets**. From e9e238a15741c6eb0d0f6ec165ef6cada57e37fc Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:52:46 -0700 Subject: [PATCH 107/118] claude code improvements --- 04-materials/05-developing-with-ai.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 2c8de9cd..de9473ea 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -1193,21 +1193,12 @@ git push 1. **Start an interactive session**: ```bash - claude-code + claude ``` -2. **Provide context** by referencing files in your prompt: +2. **Send the prompt from Exercise B:** - ``` - I'm working on a JupyterLab extension. Please read these files for context: - - src/widget.ts - - jupytercon2025_extension_workshop/routes.py - - AGENTS.md - - package.json - - pyproject.toml - - [Then paste the main prompt about adding image editing capabilities] - ``` + Use the same [one-shot prompt from Exercise B](#power-and-peril-of-one-shot-prompts) to add image editing capabilities. Claude Code will read the referenced files automatically as you mention them in your prompt. 3. **Review and apply changes**: - Claude Code will show diffs for each file @@ -1222,6 +1213,9 @@ git push Can you also run `jlpm build` to verify this compiles? ``` +or run the commands **inside** Claude Code by triggering bash mode with `!`. +Claude Code will see your command and outputs and might use them later, i.e. for debugging + **Ask for explanations:** ``` From 3995d1785c1cac79296459a0f4666367d87836fd Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:56:11 -0700 Subject: [PATCH 108/118] Update 03-vocabulary.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 03-vocabulary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index 16c7e012..a9c179e6 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -107,7 +107,7 @@ These models are characterized by having billions of parameters and the ability Agentic AI : AI systems that can autonomously take actions like an agent, rather than just providing suggestions. -The key differentiator is **tool use**โ€”agentic AI can execute commands (build, test, format), read files, search codebases, and make changes directly. +The key differentiator is **tool use**. Agentic AI can execute commands (build, test, format), read files, search codebases, and make changes directly. These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next stepโ€”iterating until the task is complete. Examples include Cursor, Claude Code, and Cline. Unlike chat-based or autocomplete AI, agentic tools act like a team member with toolsโ€”they don't just suggest, they do. From a7189dfaa7943392cd8bdf8504daec3254e930ef Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:56:43 -0700 Subject: [PATCH 109/118] Update 03-vocabulary.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 03-vocabulary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index a9c179e6..b7a5225a 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -108,7 +108,7 @@ These models are characterized by having billions of parameters and the ability Agentic AI : AI systems that can autonomously take actions like an agent, rather than just providing suggestions. The key differentiator is **tool use**. Agentic AI can execute commands (build, test, format), read files, search codebases, and make changes directly. -These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next stepโ€”iterating until the task is complete. +These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next step, iterating until the task is complete. Examples include Cursor, Claude Code, and Cline. Unlike chat-based or autocomplete AI, agentic tools act like a team member with toolsโ€”they don't just suggest, they do. From 74185131d82b15a9b8b5aa50b7a56b8e96779597 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:57:28 -0700 Subject: [PATCH 110/118] Update 03-vocabulary.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 03-vocabulary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index b7a5225a..cb33d0d7 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -110,7 +110,7 @@ Agentic AI The key differentiator is **tool use**. Agentic AI can execute commands (build, test, format), read files, search codebases, and make changes directly. These tools work in an **agentic loop**: they plan an action, use a tool to execute it, observe the result, and decide the next step, iterating until the task is complete. Examples include Cursor, Claude Code, and Cline. -Unlike chat-based or autocomplete AI, agentic tools act like a team member with toolsโ€”they don't just suggest, they do. +Unlike chat-based or autocomplete AI, agentic tools act like a team member with tools โ€” they don't just suggest, they do. LLM token : In the context of {term}`LLMs `, a token is a unit of text (roughly 3-4 characters or ~0.75 words in English) that the model processes. From 5b9d8844b86c5915d0a5e410488a62de817b8375 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 12:58:41 -0700 Subject: [PATCH 111/118] Update 04-materials/05-developing-with-ai.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 04-materials/05-developing-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index de9473ea..f3a5abc0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -231,7 +231,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat ### ๐Ÿ“ฆ Repo For this module, we will start with an existing extension that we built in chapter 2. If you are not caught up or just joining us for the afternoon session, please grab a reference implementation from [our demo repository](https://github.com/mfisher87/jupytercon2025-developingextensions-demo). -In {doc}`02-anatomy-of-extensions`, we started off by cloning an official [JupyterLab extension template](https://github.com/jupyterlab/extension-template). +In [](./02-anatomy-of-extensions.md), we started off by cloning an official [JupyterLab extension template](https://github.com/jupyterlab/extension-template). This template was recently enhanced to include AI-specific configurations and rulesets. Then, we built a JupyterLab extension that displays random images with captions from a curated collection. From a800dc410892d7771d3373a5ba349293bd2a750f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 14:52:45 -0700 Subject: [PATCH 112/118] address Matt's comment on AI vs LLM terminology --- 03-vocabulary.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/03-vocabulary.md b/03-vocabulary.md index cb33d0d7..05db1708 100644 --- a/03-vocabulary.md +++ b/03-vocabulary.md @@ -100,7 +100,8 @@ In the context of AI development, VRAM is crucial for loading and running large Typical consumer GPUs have between 4GB to 24GB of VRAM, with high-end models offering more. LLM -: Large Language Model - a type of artificial intelligence model trained on vast amounts of text data to understand and generate human-like text. +: Large Language Model - a type of AI model trained on vast amounts of text data to understand and generate human-like text. +When we say "AI" in this workshop, we're referring to LLMs. LLMs can assist with coding tasks like writing code, debugging, refactoring, and explaining complex concepts. Examples include GPT-5, Claude, and Qwen. These models are characterized by having billions of parameters and the ability to perform diverse language tasks without task-specific training. From 625bcc33a0ae47e9a3bae8b26ed5c0166dbd4ea8 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:02:53 -0700 Subject: [PATCH 113/118] flatten nested bullet points --- 04-materials/05-developing-with-ai.md | 85 ++++++++++++++++----------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index f3a5abc0..8fb24c32 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -60,40 +60,57 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) ### โ˜๏ธ Where {term}`LLMs ` live: deployment models -**Frontier Models (Cloud-Hosted):** -- **Deployment:** Run on massive server infrastructure by model providers -- **Access:** Pay-per-{term}`token ` via API keys -- **Examples:** - - **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per 1M tokens - - **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per 1M tokens - - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal, \$1.25/\$10 per 1M tokens - - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per 1M tokens -- **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed -- **Cons:** Requires internet connection, ongoing costs, data leaves your machine - -**Mid-tier and efficient models (cloud or local):** -- **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware -- **Examples:** - - **Claude Haiku** - - **Qwen3-30B-A3B** (approaching GPT-4o performance), - - **Mistral Small 3.2** - - **Llama 3.3-70B** -- **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency -- **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) - - -**Open-source & open-weight models (2025 state-of-the-art):** -- **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use -- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) -- **Examples:** - - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning - - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware - - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized - - **Qwen3-Coder**: Specialized for code generation tasks - - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) - - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly -- **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones -- **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) +#### Frontier Models (Cloud-Hosted) + +**Deployment:** Run on massive server infrastructure by model providers + +**Access:** Pay-per-{term}`token ` via API keys + +**Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed + +**Cons:** Requires internet connection, ongoing costs, data leaves your machine + +**Examples:** + +- **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per 1M tokens +- **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per 1M tokens +- **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal, \$1.25/\$10 per 1M tokens +- **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per 1M tokens + +#### Mid-tier and Efficient Models (Cloud or Local) + +**Deployment:** Can run on cloud APIs or self-hosted on consumer hardware + +**Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency + +**Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) + +**Examples:** + +- **Claude Haiku** +- **Qwen3-30B-A3B** (approaching GPT-4o performance) +- **Mistral Small 3.2** +- **Llama 3.3-70B** + + +#### Open-Source & Open-Weight Models (2025 State-of-the-Art) + +**Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use + +**Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) + +**Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones + +**Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) + +**Examples:** + +- **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning +- **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware +- **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized +- **Qwen3-Coder**: Specialized for code generation tasks +- **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) +- **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly :::{dropdown} ๐Ÿ’ก Self-Hosting {term}`LLMs ` (Optional) If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: From 38dde9c13d6686015f0fd913cbf8c4d75024150c Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:09:25 -0700 Subject: [PATCH 114/118] Update 01-prerequisites.md Co-authored-by: Matt Fisher <3608264+mfisher87@users.noreply.github.com> --- 01-prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01-prerequisites.md b/01-prerequisites.md index 72a84943..64a2473a 100644 --- a/01-prerequisites.md +++ b/01-prerequisites.md @@ -31,7 +31,7 @@ the `gh auth login` command. Feel free to use your favorite. Any command-line or graphical editor is fine! -VSCode is a safe choice. +[VSCode](https://code.visualstudio.com/) is a safe choice. For the AI-enabled development portion, [Cursor](https://cursor.com/) is another good choice! From 24abe1847926f1c04e14af346c2c182dfbc5ce1f Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:19:34 -0700 Subject: [PATCH 115/118] Revert "flatten nested bullet points" --- 04-materials/05-developing-with-ai.md | 85 +++++++++++---------------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 8fb24c32..f3a5abc0 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -60,57 +60,40 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) ### โ˜๏ธ Where {term}`LLMs ` live: deployment models -#### Frontier Models (Cloud-Hosted) - -**Deployment:** Run on massive server infrastructure by model providers - -**Access:** Pay-per-{term}`token ` via API keys - -**Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed - -**Cons:** Requires internet connection, ongoing costs, data leaves your machine - -**Examples:** - -- **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per 1M tokens -- **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per 1M tokens -- **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal, \$1.25/\$10 per 1M tokens -- **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per 1M tokens - -#### Mid-tier and Efficient Models (Cloud or Local) - -**Deployment:** Can run on cloud APIs or self-hosted on consumer hardware - -**Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency - -**Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) - -**Examples:** - -- **Claude Haiku** -- **Qwen3-30B-A3B** (approaching GPT-4o performance) -- **Mistral Small 3.2** -- **Llama 3.3-70B** - - -#### Open-Source & Open-Weight Models (2025 State-of-the-Art) - -**Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use - -**Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) - -**Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones - -**Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) - -**Examples:** - -- **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning -- **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware -- **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized -- **Qwen3-Coder**: Specialized for code generation tasks -- **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) -- **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly +**Frontier Models (Cloud-Hosted):** +- **Deployment:** Run on massive server infrastructure by model providers +- **Access:** Pay-per-{term}`token ` via API keys +- **Examples:** + - **Claude Sonnet 4.5** (Anthropic): Best coding model, \$3/\$15 per 1M tokens + - **GPT-5** (OpenAI): Best overall reasoning, \$1.25/\$10 per 1M tokens + - **Gemini 2.5 Pro** (Google): Best for speed/context (1M tokens), multimodal, \$1.25/\$10 per 1M tokens + - **Claude 4 Opus** (Anthropic): Best for long-horizon coding (30+ hour tasks), \$15/\$75 per 1M tokens +- **Pros:** State-of-the-art capabilities, specialized for different tasks (coding vs reasoning vs speed), no local compute needed +- **Cons:** Requires internet connection, ongoing costs, data leaves your machine + +**Mid-tier and efficient models (cloud or local):** +- **Deployment:** Can run on cloud APIs or self-hosted on consumer hardware +- **Examples:** + - **Claude Haiku** + - **Qwen3-30B-A3B** (approaching GPT-4o performance), + - **Mistral Small 3.2** + - **Llama 3.3-70B** +- **Pros:** Lower cost or free (if self-hosted), faster responses, good balance of capability and efficiency +- **Cons:** Less capable than frontier models, self-hosting requires GPU resources (typically 16GB+ {term}`VRAM`) + + +**Open-source & open-weight models (2025 state-of-the-art):** +- **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use +- **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) +- **Examples:** + - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning + - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware + - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized + - **Qwen3-Coder**: Specialized for code generation tasks + - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) + - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly +- **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones +- **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) :::{dropdown} ๐Ÿ’ก Self-Hosting {term}`LLMs ` (Optional) If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: From 5e52a83b145839a4ec1764d3c58b480de35626a0 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:25:06 -0700 Subject: [PATCH 116/118] Clarify LLM parameters and hardware requirements - Spell out abbreviations: 235B (billion) parameters, 262K (thousand) tokens - Add practical hardware context: 'runs on consumer hardware (laptops with 16GB+ RAM)' - Link to LLM token glossary term to avoid confusion with JupyterLab tokens - Make Cons section more explicit about laptop requirements --- 04-materials/05-developing-with-ai.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index f3a5abc0..53857ccd 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -86,14 +86,14 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) - **Licenses:** Vary from fully open (Apache 2.0, MIT) to restricted commercial use - **Deployment:** Can be self-hosted using tools like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/), or [vLLM](https://github.com/vllm-project/vllm) - **Examples:** - - **Qwen3-235B-A22B** (Apache 2.0 license): 235B params with 22B active, 262K context, exceptional reasoning - - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware - - **GLM-4.5 Air**: Optimized for 48GB RAM laptops when quantized + - **Qwen3-235B-A22B** (Apache 2.0 license): 235 billion (235B) parameters with 22B active, 262 thousand (262K) {term}`token ` context, exceptional reasoning + - **GLM-4.5** (Open License): Strong coding and agentic abilities, runs on consumer hardware (laptops with 16GB+ RAM) + - **GLM-4.5 Air**: Optimized for laptops with 48GB RAM when quantized - **Qwen3-Coder**: Specialized for code generation tasks - - **DeepSeek-R1**: 671B params (37B active), MIT license, advanced reasoning (86.7% on AIME) - - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, consumer-friendly + - **DeepSeek-R1**: 671B parameters (37B active), MIT license, advanced reasoning (86.7% on AIME) + - **OpenAI GPT-OSS-120B/20B** (Apache 2.0): Near o4-mini performance, runs on consumer hardware - **Pros:** Full control, no API costs, data stays local, latest open models often outperform closed frontier ones -- **Cons:** Requires technical setup and adequate hardware (16GB+ VRAM for smaller models, 48GB+ for larger ones) +- **Cons:** Requires technical setup and adequate hardware (typically a laptop with 16GB+ {term}`VRAM` for smaller models, 48GB+ for larger ones) :::{dropdown} ๐Ÿ’ก Self-Hosting {term}`LLMs ` (Optional) If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: From 7adf0ca179111603478dd6dc1878f03f90c81517 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:29:56 -0700 Subject: [PATCH 117/118] Address RRosio's comments --- 04-materials/05-developing-with-ai.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index 53857ccd..d2d22567 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -98,6 +98,12 @@ AI coding assistants are powered by **Large Language Models ({term}`LLMs `) :::{dropdown} ๐Ÿ’ก Self-Hosting {term}`LLMs ` (Optional) If you want to run models locally (for privacy or cost savings), tools like [Ollama](https://ollama.com/) make it easy: +Privacy basics: When we say "for privacy," we mean you can keep prompts, code, and any sample data on your machine rather than sending them to a thirdโ€‘party API. This reduces the risk of accidental disclosure and can help with compliance when handling sensitive data (PII, credentials, customer data). You should still follow your organization's policies (for example: scrub sensitive inputs, review telemetry/logging settings, and restrict network egress during development). + +Learn more: +- [OWASP Top 10 for LLM Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/) โ€” common risks like data leakage and prompt injection +- [NIST AI Risk Management Framework (AI RMF 1.0)](https://www.nist.gov/itl/ai-risk-management-framework) โ€” governance and privacy guidance for AI systems + ```bash # Install Ollama # macOS: Download from https://ollama.com/download/mac From 6f428d4840ca876304469769d099dbf825957b01 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Thu, 30 Oct 2025 16:48:01 -0700 Subject: [PATCH 118/118] update claude code installation instructions --- 04-materials/05-developing-with-ai.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/04-materials/05-developing-with-ai.md b/04-materials/05-developing-with-ai.md index d2d22567..211a011e 100644 --- a/04-materials/05-developing-with-ai.md +++ b/04-materials/05-developing-with-ai.md @@ -219,7 +219,7 @@ We'll work with **Cursor** to demonstrate the AI-assisted workflow, then repeat ::::{dropdown} Alternatives - [Gemini CLI](https://github.com/google-gemini/gemini-cli) (free tier available), [Cline](https://github.com/cline/cline) (VS Code extension with CLI mode, free), [Continue](https://github.com/continuedev/continue) (IDE/terminal/CI agent, free), [Plandex](https://github.com/plandex-ai/plandex) (designed for large projects), [aichat](https://github.com/sigoden/aichat) (all-in-one LLM CLI), [GitHub Copilot CLI](https://github.com/github/copilot-cli), [Aider](https://github.com/Aider-AI/aider) (Git-integrated, open-source), [Google Jules](https://jules.google/) (async background agent, beta) :::: -- **Install:** Requires Node.js, then `npm install --global @anthropic-ai/claude-code` +- **Install:** See [official setup instructions](https://docs.claude.com/en/docs/claude-code/setup) :::{note} Further Reading :class: dropdown @@ -354,19 +354,25 @@ We recommend you sign up for a free Hobby plan for this workshop! You'll have on #### โŒจ๏ธ Setting up Claude Code -1. **Use your workshop environment** - - You already have Node.js 22 installed in your `jupytercon2025` environment - - Make sure the environment is activated: - ```bash - micromamba activate jupytercon2025 - ``` +1. **Install Claude Code** + + Follow the [official setup instructions](https://docs.claude.com/en/docs/claude-code/setup) for your operating system. -2. **Install Claude Code** + **Recommended: Native installers** + - **macOS/Linux:** `curl -fsSL https://claude.ai/install.sh | bash` + - **Windows PowerShell:** `irm https://claude.ai/install.ps1 | iex` + + **Alternative: npm (lives in your environment)** + + If you already have Node.js 22 installed in your `jupytercon2025` environment: ```bash + micromamba activate jupytercon2025 npm install --global @anthropic-ai/claude-code ``` -3. **Set up AWS Bedrock authentication** + See the [full installation guide](https://docs.claude.com/en/docs/claude-code/setup) for all options. + +2. **Set up AWS Bedrock authentication** We will be using Claude models provided by AWS Bedrock in this tutorial.