-
-
Notifications
You must be signed in to change notification settings - Fork 28
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Propose adding an optional Copier question that, when enabled, scaffolds a small, vendor-neutral set of AI assistant guidelines for extension development ("AI rules"). If the user opts in, the template also drops tool-specific helper files (e.g., .cursorrules
, CLAUDE.md
) to improve the out-of-the-box experience with popular AI pair-programmers without coupling the template to any vendor.
Problem
Many extension authors now use AI assistants when starting from this template. A light set of rules (naming, typing, no-any, no console.log, API patterns, IDs) nudges assistants toward community-aligned code and reduces review churn. Today, every team re-creates similar rules ad hoc.
Proposed Solution
- New Copier question (default = No):
- Prompt: "Include AI assistant rules and helper files?"
- Default:
false
to keep the current behavior and avoid vendor implications.
- When
true
, scaffold:- Vendor-neutral doc: AI_RULES.md (concise coding standards aligned with JupyterLab examples: typed TS, consistent plugin/command IDs, RESTful server handlers, no console.log, no any, error handling, CSS namespacing, etc.).
- Tool-specific helpers (optional files generated by the same toggle):
.cursorrules
— minimal JSON/YAML with enforceable patterns the Cursor ecosystem understands (e.g., disallow console.log, : any).CLAUDE.md
— prompt/guidance file with the same rules phrased for Claude (or any LLM that reads a project “guide” file).README
snippet: one short paragraph linking to AI_RULES.md and explaining the optional files; explicitly vendor-neutral.
- No runtime dependencies. These are docs/config files only; they do not alter build, packaging, or test behavior.
Additional context
Example rule
template/.cursor/rules/jupyterlab-extension.mdc.jinja
---
description: JupyterLab Extension Development – coding standards, naming, integration, and project alignment
globs: ["*.py", "*.ts", "*.tsx", "*.js", "*.jsx", "*.json", "*.md", "pyproject.toml", "package.json"]
alwaysApply: true
---
### JupyterLab Extension Development – Project Rules
These rules guide AI-generated code to align with JupyterLab community standards and keep the extension maintainable. They adapt based on the selected extension kind.
Kind selected: {{ kind }}
## Enforceable Rules
- **No `console.log`**: Avoid `console.log` in favor of structured logging or user-facing notifications.
- **No `any` type**: Do not use the `any` type in TypeScript; define explicit interfaces or types instead.
```json
{
"rules": [
{
"name": "no-console-log",
"description": "Disallow console.log statements in TypeScript/JavaScript files.",
"globs": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"prohibitedPatterns": ["console.log"]
},
{
"name": "no-any-type",
"description": "Disallow the `any` type in TypeScript files.",
"globs": ["*.ts", "*.tsx"],
"prohibitedPatterns": [": any", " = any"]
}
]
}
```
## Coding Standards
{% if kind == 'frontend-and-server' %}- **Python (PEP 8)**: Use 4-space indentation and meaningful names. Class names use CamelCase; functions, variables, and methods use lowercase_with_underscores. Private/internal names use a single leading underscore. Align with Jupyter’s coding style guidelines.
{% endif %}- **TypeScript/JavaScript**: Use PascalCase for class and interface names (e.g., `MyPanelWidget`) and camelCase for functions, methods, and variables. Avoid `any` whenever possible—prefer explicit types. Use Prettier/ESLint defaults (2-space indent, etc.) for clean, uniform formatting.
- **Descriptive naming and comments**: Choose clear, descriptive names for classes, commands, and modules (e.g., `DataUploadHandler` instead of `MyHandler`). Include JSDoc for TS and{% if kind == 'frontend-and-server' %} docstrings for Python{% endif %} to describe the purpose of modules, classes, and complex functions.
- **No unused or duplicate code**: {% if kind == 'frontend-and-server' %}Avoid duplicating logic across front and back ends. Centralize processing when possible and pass data across the boundary. {% endif %}Do not produce dead code or leave TODOs—implement features fully or not at all.
## Naming and Project Structure
{% if kind == 'frontend-and-server' %}- **Python package names**: Use short, all-lowercase names without dashes. Use underscores if needed (e.g., `jupyterlab_myext`). For the PyPI distribution name, using a dash is acceptable/common (e.g., `jupyterlab-myext`) as long as the importable Python module uses underscores.
{% endif %}- **Frontend package name**: Use the same base name as the project for the npm package (e.g., `"jupyterlab-myext"` or `@<organization>/myext`). Keep naming consistent to avoid confusion.
- **Plugin and command IDs**: Prefix with your extension name. Example plugin id: `'<your-ext-name>:plugin'`; command IDs like `'<your-ext-name>:do-action'`. Keep IDs lowercase; use hyphens or camelCase for multi-word actions (e.g., `myext:open-file`).
- **File and class names**: Name sources after their function. For a React widget, `MyWidget.tsx` containing class `MyWidget` is appropriate. Avoid catch-all files like `utils.ts`; partition logically (e.g., `api.ts` for API calls{% if kind == 'frontend-and-server' %}, `handlers.py` for Tornado handlers{% endif %}). Organize frontend under `src/`{% if kind == 'frontend-and-server' %} and Python code in the package directory{% endif %}.
{% if kind == 'theme' %}## Theme Extensions
- **No backend**: Theme extensions are frontend-only. Keep all assets under `style/`.
- **CSS organization**: Use `style/variables.css` for custom properties and `style/index.css` for imports and global theme selectors. Avoid global resets; extend JupyterLab tokens.
- **Support light and dark**: Scope colors with attribute selectors like `[data-jp-theme-light='true']` and `[data-jp-theme-light='false']`.
- **Register the theme**: Register via `IThemeManager` and load CSS from `style/index.css`.
```ts
import { IThemeManager } from '@jupyterlab/apputils';
import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';
const plugin: JupyterFrontEndPlugin<void> = {
id: '<your-ext-name>:theme',
autoStart: true,
requires: [IThemeManager],
activate: (app: JupyterFrontEnd, manager: IThemeManager) => {
const style = 'style/index.css';
manager.register({
name: '<Human Theme Name>',
isLight: true, // set appropriately
load: () => manager.loadCSS(style),
unload: () => Promise.resolve()
});
}
};
export default plugin;
```
- **Use CSS variables**: Prefer CSS custom properties to ensure compatibility with other extensions and JupyterLab updates.
{% endif %}
{% if kind == 'mimerender' %}## MIME Renderer Extensions
- **No backend**: MIME renderers are frontend-only. Implement an `IRenderMime.IExtension` to render a MIME type.
- **Renderer factory**: Provide `mimeTypes`, `dataType` (`'json' | 'string' | 'object'`), and `safe` indicating whether the renderer is safe for untrusted content.
- **Widget implementation**: Extend `Widget` and implement `renderModel`.
```ts
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
import { Widget } from '@lumino/widgets';
class MyRenderer extends Widget implements IRenderMime.IRenderer {
constructor(options: IRenderMime.IRendererOptions) {
super();
this.addClass('myext-Renderer');
}
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
const data = model.data['text/plain'] as string;
this.node.textContent = data;
}
}
const extension: IRenderMime.IExtension = {
id: '<your-ext-name>:renderer',
rendererFactory: {
safe: true,
mimeTypes: ['text/plain'],
createRenderer: opts => new MyRenderer(opts)
},
rank: 50,
dataType: 'string'
};
export default extension;
```
- **Security**: If using HTML, sanitize before injecting. Set `safe: false` only when necessary and handle untrusted content carefully.
- **Styling**: Namespace CSS classes (e.g., `myext-*`). Avoid mutating global styles.
- **Performance**: Keep `renderModel` fast; avoid blocking the main thread. Use async operations where appropriate.
{% endif %}
{% if kind == 'frontend-and-server' %}## Backend–Frontend Integration
- **RESTful endpoints with Tornado**: Create a handler extending Jupyter Server’s `APIHandler` and register routes on startup.
```python
from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join
class HelloWorldHandler(APIHandler):
def get(self):
self.finish({"data": "Hello, world!"})
def setup_handlers(web_app):
host_pattern = r".*$"
base_url = web_app.settings.get("base_url", "/")
route_pattern = url_path_join(base_url, "myext", "hello")
web_app.add_handlers(host_pattern, [(route_pattern, HelloWorldHandler)])
```
- **HTTP methods**: `GET` for retrieval, `POST`/`PUT` for actions/updates. Use `self.get_json_body()` or `self.get_body_argument()` to read request data.
- **Frontend calls via `requestAPI`**: Call your server endpoints from TypeScript.
```ts
import { requestAPI } from './handler';
interface HelloResponse {
data: string;
status?: 'success' | 'error';
}
export async function fetchHello(): Promise<string> {
try {
const response = await requestAPI<HelloResponse>('hello', {
method: 'GET'
});
if (response.status === 'error') {
throw new Error('Server returned error status');
}
return response.data;
} catch (err) {
// Re-throw the error to allow the caller to handle it,
// optionally wrapping it in a custom error type.
throw new Error(`API request failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
}
}
```
- **Keep back and front in sync**: When server responses change, update TS types and UI accordingly. Avoid unused routes and calls to nonexistent endpoints.
- **Consistent JSON naming**: Use the same keys on both sides (e.g., `{"result": ...}` ↔ `response.result`). Consider shared constants for routes and command names.
{% endif %}
## Development builds
{% set is_full_stack = kind|lower == 'frontend-and-server' %}
### Setup (run once)
```bash
pip install -e ".{% if test and is_full_stack %}[test]{% endif %}" && jupyter labextension develop . --overwrite{% if is_full_stack %} && jupyter server extension enable {{ python_name }}{% endif %} && jlpm build
```
### Rebuild during development
```bash
# Frontend changes
jlpm build
{% if is_full_stack %}# Backend (Python) changes
pip install -e .
{% endif %}
```
### Debug
```bash
jupyter labextension list && jupyter server extension list
```
### Clean / Reset (if things get out of sync)
```bash
jlpm clean && jlpm build
jupyter labextension develop . --overwrite
{% if is_full_stack %}pip install -e .{% endif %}
```
Notes & Tips:
- Run commands inside the same environment where JupyterLab is installed (conda/mamba/venv). If `jlpm` is not found, verify your environment.
- Prefer `jlpm` (JupyterLab’s pinned Yarn). Do not mix `yarn.lock` with `package-lock.json` to prevent resolution issues.
- Restart JupyterLab if changes don’t appear (`Ctrl+C` then `jupyter lab`).
## Best Practices and Template Alignment
- **Leverage the project structure**: Keep changes aligned with the project’s scaffold. Do not rename or move core files without updating configuration. {% if kind == 'frontend-and-server' %}Use `handlers.py` for API handlers and the provided server setup.{% else %}Place UI logic in `src/` and organize modules by feature.{% endif %}
- **Version consistency**: {% if kind == 'frontend-and-server' %}Keep Python package and npm package versions in sync for releases.{% else %}Maintain semantic versioning in `package.json` and update consistently across release artifacts.{% endif %}
- **Follow community examples**: Start minimal (e.g., “Server Hello World” for combined extensions or simple UI command for frontend-only) and iterate. Use JupyterLab APIs and patterns shown in official examples.
- **Thorough testing**: Exercise features in a running JupyterLab. Use browser console{% if kind == 'frontend-and-server' %} and server logs{% endif %} for debugging. Add unit/integration tests for non-trivial features.
## Common Pitfalls to Avoid
- **Mixing package managers**: Use only `jlpm`/`yarn` or only `npm`. Don't mix `yarn.lock` and `package-lock.json`.
- **Hardcoded paths**: Use `url_path_join()` for server routes and relative imports for frontend modules.
- **Missing error handling**: Always wrap API calls in try-catch blocks and provide user feedback.
- **CSS conflicts**: Namespace all CSS classes with your extension name (e.g., `.myext-widget`).
- **Memory leaks**: Dispose of widgets and disconnect signals properly in `dispose()` methods.{% if kind == 'frontend-and-server' %}
- **CORS issues**: Ensure server handlers inherit from `APIHandler` for proper CORS handling.
- **Python import errors**: Use relative imports within your package; absolute imports for external dependencies.{% endif %}
### Quick Reference
Refer to the commands in the "Development builds" section above.
**Key Patterns:**
- Plugin ID: `'{{ python_name }}:plugin'`
- Command ID: `'{{ python_name }}:command-name'`
- CSS classes: `.{{ python_name | replace('_', '-') }}-ClassName`
Backwards compatibility
- No change for users who don’t opt in.
- Purely additive files when enabled.
Vendor neutrality
- The question and docs are vendor-neutral.
- Tool-specific files are optional artifacts emitted by the same toggle to reduce friction for users who already rely on those assistants.
- We do not maintain vendor integrations; we only ship small text files.
Alternatives considered
- Host the rules in a separate “starter-rules” repo and link from the template (adds friction).
- Add only AI_RULES.md and omit tool-specific files (lower immediate utility for common assistants).
Open questions for maintainers
- Preferred filenames and placement (AI_RULES.md at root vs docs/).
- Scope/length of the neutral rules (keep to one pager?).
- Which tool files to include initially (Cursor and Claude?), and how to frame this in README to stay neutral.
- Testing/documentation expectations (a small unit in the docs site, or README-only?).
Acceptance criteria (proposal)
- New Copier question added (default false)
- AI_RULES.md generated when true
- Optional .cursorrules and CLAUDE.md generated when true
- README gains a 2–3 sentence section linking to AI_RULES.md
- No changes to build/test/publish flows when disabled or enabled
Prior art / Reference
- Example fork implementing this approach (Cursor rules + neutral guidance): orbrx/extension-template — fork of this repo with AI rules added to the template
If the direction looks good, I can submit a PR that:
- Adds the Copier question + conditional file templates
- Contributes a concise AI_RULES.md
- Wires a short README blurb
Thanks for considering!
krassowski
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request