jupyterlab-workshop 0.1.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. jupyterlab_workshop-0.1.13/.gitignore +58 -0
  2. jupyterlab_workshop-0.1.13/.prettierignore +15 -0
  3. jupyterlab_workshop-0.1.13/.python-version +1 -0
  4. jupyterlab_workshop-0.1.13/.readthedocs.yaml +15 -0
  5. jupyterlab_workshop-0.1.13/.yarnrc.yml +2 -0
  6. jupyterlab_workshop-0.1.13/AGENTS.md +293 -0
  7. jupyterlab_workshop-0.1.13/CLAUDE.md +1 -0
  8. jupyterlab_workshop-0.1.13/CONTRIBUTING.md +128 -0
  9. jupyterlab_workshop-0.1.13/Justfile +130 -0
  10. jupyterlab_workshop-0.1.13/LICENSE +202 -0
  11. jupyterlab_workshop-0.1.13/PKG-INFO +104 -0
  12. jupyterlab_workshop-0.1.13/README.md +69 -0
  13. jupyterlab_workshop-0.1.13/TESTING.md +96 -0
  14. jupyterlab_workshop-0.1.13/binder/postBuild +32 -0
  15. jupyterlab_workshop-0.1.13/binder/requirements.txt +8 -0
  16. jupyterlab_workshop-0.1.13/binder/runtime.txt +1 -0
  17. jupyterlab_workshop-0.1.13/collections/catalog.json +23 -0
  18. jupyterlab_workshop-0.1.13/collections/examples/collection.json +84 -0
  19. jupyterlab_workshop-0.1.13/collections/examples/icon.svg +6 -0
  20. jupyterlab_workshop-0.1.13/dev/overrides.json +9 -0
  21. jupyterlab_workshop-0.1.13/docs/_static/author-mode.png +0 -0
  22. jupyterlab_workshop-0.1.13/docs/_static/browser.png +0 -0
  23. jupyterlab_workshop-0.1.13/docs/_static/collections-dialog.png +0 -0
  24. jupyterlab_workshop-0.1.13/docs/_static/finish-dialog.png +0 -0
  25. jupyterlab_workshop-0.1.13/docs/_static/panel.png +0 -0
  26. jupyterlab_workshop-0.1.13/docs/_static/trust-dialog.png +0 -0
  27. jupyterlab_workshop-0.1.13/docs/actions.md +121 -0
  28. jupyterlab_workshop-0.1.13/docs/analytics.md +66 -0
  29. jupyterlab_workshop-0.1.13/docs/authoring.md +135 -0
  30. jupyterlab_workshop-0.1.13/docs/checks.md +245 -0
  31. jupyterlab_workshop-0.1.13/docs/cli.md +237 -0
  32. jupyterlab_workshop-0.1.13/docs/collections.md +404 -0
  33. jupyterlab_workshop-0.1.13/docs/concepts.md +141 -0
  34. jupyterlab_workshop-0.1.13/docs/conf.py +40 -0
  35. jupyterlab_workshop-0.1.13/docs/demo.md +59 -0
  36. jupyterlab_workshop-0.1.13/docs/deploying.md +204 -0
  37. jupyterlab_workshop-0.1.13/docs/environment.md +73 -0
  38. jupyterlab_workshop-0.1.13/docs/getting-started.md +187 -0
  39. jupyterlab_workshop-0.1.13/docs/index.md +178 -0
  40. jupyterlab_workshop-0.1.13/docs/layouts.md +113 -0
  41. jupyterlab_workshop-0.1.13/docs/lite.md +68 -0
  42. jupyterlab_workshop-0.1.13/docs/pages.md +229 -0
  43. jupyterlab_workshop-0.1.13/docs/platforms.md +156 -0
  44. jupyterlab_workshop-0.1.13/docs/publishing.md +187 -0
  45. jupyterlab_workshop-0.1.13/docs/settings.md +62 -0
  46. jupyterlab_workshop-0.1.13/docs/troubleshooting.md +125 -0
  47. jupyterlab_workshop-0.1.13/docs/trust.md +128 -0
  48. jupyterlab_workshop-0.1.13/docs/tutorial.md +358 -0
  49. jupyterlab_workshop-0.1.13/docs/using.md +176 -0
  50. jupyterlab_workshop-0.1.13/docs/variables.md +188 -0
  51. jupyterlab_workshop-0.1.13/eslint.config.mjs +70 -0
  52. jupyterlab_workshop-0.1.13/examples/git-basics/README.md +8 -0
  53. jupyterlab_workshop-0.1.13/examples/git-basics/pages/01-create-a-repository.md +73 -0
  54. jupyterlab_workshop-0.1.13/examples/git-basics/pages/02-first-commit.md +109 -0
  55. jupyterlab_workshop-0.1.13/examples/git-basics/pages/03-edit-and-diff.md +65 -0
  56. jupyterlab_workshop-0.1.13/examples/git-basics/pages/04-branch-and-merge.md +75 -0
  57. jupyterlab_workshop-0.1.13/examples/git-basics/pages/05-resolve-a-conflict.md +139 -0
  58. jupyterlab_workshop-0.1.13/examples/git-basics/verify/merged.py +22 -0
  59. jupyterlab_workshop-0.1.13/examples/git-basics/workshop.yaml +51 -0
  60. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/README.md +8 -0
  61. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/files/notes.md +3 -0
  62. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/01-welcome.md +57 -0
  63. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/02-notebooks.md +71 -0
  64. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/03-kernels.md +49 -0
  65. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/04-files.md +88 -0
  66. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/05-variables.md +57 -0
  67. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/06-conda.md +16 -0
  68. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/06-pip.md +17 -0
  69. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/07-automation.md +47 -0
  70. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/pages/08-finish.md +18 -0
  71. jupyterlab_workshop-0.1.13/examples/hello-jupyterlab/workshop.yaml +41 -0
  72. jupyterlab_workshop-0.1.13/examples/workshop-authoring/.gitignore +5 -0
  73. jupyterlab_workshop-0.1.13/examples/workshop-authoring/pages/01-what-a-workshop-is.md +52 -0
  74. jupyterlab_workshop-0.1.13/examples/workshop-authoring/pages/02-scaffold.md +62 -0
  75. jupyterlab_workshop-0.1.13/examples/workshop-authoring/pages/03-write-a-page.md +81 -0
  76. jupyterlab_workshop-0.1.13/examples/workshop-authoring/pages/04-lint.md +115 -0
  77. jupyterlab_workshop-0.1.13/examples/workshop-authoring/pages/05-test-and-publish.md +65 -0
  78. jupyterlab_workshop-0.1.13/examples/workshop-authoring/workshop.yaml +34 -0
  79. jupyterlab_workshop-0.1.13/github-pages/index.html +147 -0
  80. jupyterlab_workshop-0.1.13/install.json +5 -0
  81. jupyterlab_workshop-0.1.13/jupyter-config/server-config/jupyterlab_workshop.json +7 -0
  82. jupyterlab_workshop-0.1.13/jupyterlab_workshop/__init__.py +37 -0
  83. jupyterlab_workshop-0.1.13/jupyterlab_workshop/_version.py +4 -0
  84. jupyterlab_workshop-0.1.13/jupyterlab_workshop/analytics.py +130 -0
  85. jupyterlab_workshop-0.1.13/jupyterlab_workshop/bridge.py +159 -0
  86. jupyterlab_workshop-0.1.13/jupyterlab_workshop/catalog.py +300 -0
  87. jupyterlab_workshop-0.1.13/jupyterlab_workshop/checks.py +412 -0
  88. jupyterlab_workshop-0.1.13/jupyterlab_workshop/cli.py +892 -0
  89. jupyterlab_workshop-0.1.13/jupyterlab_workshop/collection.py +531 -0
  90. jupyterlab_workshop-0.1.13/jupyterlab_workshop/environment.py +323 -0
  91. jupyterlab_workshop-0.1.13/jupyterlab_workshop/fetch.py +584 -0
  92. jupyterlab_workshop-0.1.13/jupyterlab_workshop/handlers.py +583 -0
  93. jupyterlab_workshop-0.1.13/jupyterlab_workshop/harness.py +626 -0
  94. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/package.json +107 -0
  95. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/schemas/@jupyterlab-workshop/labextension/package.json.orig +102 -0
  96. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/schemas/@jupyterlab-workshop/labextension/panel.json +140 -0
  97. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/232.dfaa373fb7aa8044.js +1475 -0
  98. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/338.df6d26e0a8dc72bc.js +3 -0
  99. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/504.c2ade8e76866b88c.js +16 -0
  100. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/57.bb504983081811aa.js +1 -0
  101. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/980.5a55f6a96241b720.js +38 -0
  102. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/remoteEntry.6e0d9fe79bf4ac85.js +1 -0
  103. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/style.js +4 -0
  104. jupyterlab_workshop-0.1.13/jupyterlab_workshop/labextension/static/third-party-licenses.json +34 -0
  105. jupyterlab_workshop-0.1.13/jupyterlab_workshop/lite.py +344 -0
  106. jupyterlab_workshop-0.1.13/jupyterlab_workshop/mcp.py +638 -0
  107. jupyterlab_workshop-0.1.13/jupyterlab_workshop/nodejs/workshop-cli.cjs +16886 -0
  108. jupyterlab_workshop-0.1.13/jupyterlab_workshop/platform.py +194 -0
  109. jupyterlab_workshop-0.1.13/jupyterlab_workshop/publish.py +158 -0
  110. jupyterlab_workshop-0.1.13/jupyterlab_workshop/py.typed +0 -0
  111. jupyterlab_workshop-0.1.13/jupyterlab_workshop/scaffold.py +486 -0
  112. jupyterlab_workshop-0.1.13/jupyterlab_workshop/schema/catalog.schema.json +114 -0
  113. jupyterlab_workshop-0.1.13/jupyterlab_workshop/schema/collection.schema.json +154 -0
  114. jupyterlab_workshop-0.1.13/jupyterlab_workshop/schema/workshop.schema.json +279 -0
  115. jupyterlab_workshop-0.1.13/package.json +110 -0
  116. jupyterlab_workshop-0.1.13/pyproject.toml +146 -0
  117. jupyterlab_workshop-0.1.13/scripts/generate_manifest_reference.py +168 -0
  118. jupyterlab_workshop-0.1.13/scripts/screenshots.py +431 -0
  119. jupyterlab_workshop-0.1.13/skills/jupyterlab-workshop-authoring/SKILL.md +380 -0
  120. jupyterlab_workshop-0.1.13/skills/jupyterlab-workshop-authoring/references/actions.md +121 -0
  121. jupyterlab_workshop-0.1.13/skills/jupyterlab-workshop-authoring/references/page-template.md +82 -0
  122. jupyterlab_workshop-0.1.13/skills/jupyterlab-workshop-authoring/references/pages.md +229 -0
  123. jupyterlab_workshop-0.1.13/skills/jupyterlab-workshop-authoring/references/style-guide.md +93 -0
  124. jupyterlab_workshop-0.1.13/tsconfig.base.json +23 -0
  125. jupyterlab_workshop-0.1.13/uv.lock +2939 -0
  126. jupyterlab_workshop-0.1.13/yarn.lock +8939 -0
@@ -0,0 +1,58 @@
1
+ scratch/
2
+
3
+ # JavaScript
4
+ node_modules/
5
+ .yarn/
6
+ *.bundle.*
7
+ *.log
8
+ *.tsbuildinfo
9
+ .eslintcache
10
+ .stylelintcache
11
+ packages/*/lib/
12
+ packages/core/coverage/
13
+
14
+ # Built labextension, Node CLI bundle, schema copy and generated version file
15
+ jupyterlab_workshop/labextension/
16
+ jupyterlab_workshop/nodejs/
17
+ jupyterlab_workshop/schema/
18
+ jupyterlab_workshop/_version.py
19
+
20
+ # Documentation build outputs and the assembled GitHub Pages site
21
+ docs/_build/
22
+ docs/reference/
23
+ site/
24
+
25
+ # Integration tests
26
+ tests/ui-tests/test-results/
27
+ tests/ui-tests/playwright-report/
28
+
29
+ # Python
30
+ .venv/
31
+ __pycache__/
32
+ *.py[cod]
33
+ *.egg-info/
34
+ build/
35
+ dist/
36
+ .pytest_cache/
37
+ .mypy_cache/
38
+ .ruff_cache/
39
+ .coverage
40
+ htmlcov/
41
+ .ipynb_checkpoints/
42
+
43
+ # Editors and OS
44
+ .DS_Store
45
+
46
+ # Files created by running the example workshops
47
+ examples/*/demo/
48
+ examples/*/_workshop/
49
+ examples/*/scratch/
50
+
51
+ # Workshops downloaded through the extension
52
+ workshops/
53
+
54
+ # JupyterLite site built by `just lite`
55
+ lite-site/
56
+
57
+ # JupyterLite build state written next to a build
58
+ .jupyterlite.doit.db
@@ -0,0 +1,15 @@
1
+ node_modules
2
+ **/node_modules
3
+ **/lib
4
+ **/package.json
5
+ !/package.json
6
+ jupyterlab_workshop
7
+ eslint.config.mjs
8
+ .venv
9
+ scratch
10
+ tests/ui-tests/test-results
11
+ tests/ui-tests/playwright-report
12
+ site
13
+ lite-site
14
+ docs/_build
15
+ docs/reference
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,15 @@
1
+ # Read the Docs build. The project itself is not installed (that would need
2
+ # Node.js to build the extension); only the docs dependency group is synced
3
+ # with uv, matching `just docs`.
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.12"
10
+ commands:
11
+ - asdf plugin add uv
12
+ - asdf install uv latest
13
+ - asdf global uv latest
14
+ - uv sync --only-group docs --no-install-project
15
+ - uv run --no-sync sphinx-build -W --keep-going -b html docs $READTHEDOCS_OUTPUT/html
@@ -0,0 +1,2 @@
1
+ nodeLinker: node-modules
2
+ enableScripts: false
@@ -0,0 +1,293 @@
1
+ # Agent guidance for jupyterlab-workshop
2
+
3
+ ## Project
4
+
5
+ jupyterlab-workshop is a JupyterLab extension for running guided,
6
+ interactive workshops inside JupyterLab. Workshop instructions are shown in
7
+ a side panel and contain clickable actions that drive the live JupyterLab
8
+ session: terminals, the file browser, the editor, notebooks, kernels and
9
+ layout. Workshops can verify learner progress, gate pages on that progress,
10
+ and include forms and quizzes. The concept comes from the Educates Training
11
+ Platform (educates.dev), re-imagined so that a single workshop runs wherever
12
+ JupyterLab runs, without Kubernetes or containers. See README.md for the
13
+ project goals.
14
+
15
+ A workshop is a directory containing a `workshop.yaml` manifest and
16
+ MyST-flavoured Markdown pages. The format is text based and git friendly.
17
+
18
+ The repository is a monorepo with three main parts:
19
+
20
+ - `packages/core/` is `@jupyterlab-workshop/core`, pure TypeScript with no
21
+ JupyterLab dependencies: workshop format parsing, action definitions,
22
+ variable substitution, lint rules and JSON schemas. Keep it free of
23
+ JupyterLab imports so the CLI and other tooling can reuse it under Node.
24
+
25
+ - `packages/labextension/` is `@jupyterlab-workshop/labextension`, the
26
+ JupyterLab frontend extension: instructions panel, renderer, action
27
+ implementations, verify engine, trust manager, loader and state.
28
+
29
+ - `jupyterlab_workshop/` is the Python package: the
30
+ `jupyter_server` extension (platform detection, fetching, script
31
+ verifies, checkpoints) and the `jupyter workshop` CLI.
32
+
33
+ README.md is the long description shown on PyPI, so it stays short and
34
+ user facing and links to the documentation site; development setup and
35
+ workflow belong in CONTRIBUTING.md, and the full documentation is under
36
+ `docs/` and published on Read the Docs.
37
+
38
+ Example workshops live in `examples/` and double as test fixtures. Tests
39
+ live in `tests/` (Python and Galata UI tests) and alongside the source in
40
+ `packages/core/`. See TESTING.md for where tests are, how to run them, and
41
+ conventions for adding new ones.
42
+
43
+ The scratch/ directory is not part of the git repo. It holds temporary
44
+ working files, such as reference material given to an agent or plans an
45
+ agent is asked to generate. Its contents come and go, so never reference
46
+ scratch/ files by name from code or documentation that will be committed.
47
+
48
+ ## Tooling: always use uv and jlpm
49
+
50
+ All Python environment and package management in this project is done with
51
+ [uv](https://docs.astral.sh/uv/). Never use the Python venv module, bare
52
+ pip, or python -m build directly.
53
+
54
+ - Run commands in the project environment: `uv run <command>`
55
+ (e.g. `uv run pytest`, `uv run jupyter lab`)
56
+
57
+ - Run a Python interpreter: `uv run python`
58
+
59
+ - Build sdist and wheel: `uv build`
60
+
61
+ - Add or remove dependencies (updates pyproject.toml): `uv add <package>`,
62
+ `uv remove <package>`
63
+
64
+ - Sync the environment from pyproject.toml: `uv sync`
65
+
66
+ All JavaScript and TypeScript package management is done with `jlpm`, the
67
+ pinned Yarn that ships with JupyterLab. Never use npm or a system yarn
68
+ directly, and never commit a lockfile produced by them. jlpm is installed
69
+ into the project environment with JupyterLab, so run it as `uv run jlpm`.
70
+
71
+ - Install workspace dependencies: `uv run jlpm install`
72
+
73
+ - Add a dependency to one workspace package: run `uv run jlpm add <package>`
74
+ from inside that package's directory (for example `packages/core`)
75
+
76
+ - Build all packages: `uv run jlpm build`
77
+
78
+ ## Common tasks: use the Justfile
79
+
80
+ The Justfile defines targets for the common development tasks, wrapping
81
+ the correct uv and jlpm invocations (including details like linking the
82
+ labextension into JupyterLab in development mode and running the Galata
83
+ tests against a live server). Prefer these targets over synthesizing the
84
+ underlying commands yourself; run `just --list` to see everything.
85
+
86
+ - `just install` sets up the development environment: syncs the Python
87
+ environment, installs JavaScript dependencies and links the extension
88
+ into JupyterLab in development mode.
89
+
90
+ - `just build` builds the TypeScript packages and the labextension bundle.
91
+ `just watch` rebuilds on change; run it alongside `just lab`.
92
+
93
+ - `just lab` starts JupyterLab from the repository root with the extension
94
+ loaded, so the example workshops are reachable through the contents API.
95
+
96
+ - `just test` runs the fast test suites: the Jest tests for `packages/core`
97
+ and the pytest suite for the Python package. Extra arguments pass
98
+ through to pytest, so a specific file or test is
99
+ `just test tests/python/test_fetch.py` or `just test -k pattern`.
100
+
101
+ - `just test-core` runs only the Jest tests; `just test-python` runs only
102
+ pytest; `just test-ui` runs the Galata browser tests against a real
103
+ JupyterLab (slow; run before finishing frontend work, not on every edit).
104
+
105
+ - `just lint` checks TypeScript with eslint and prettier and Python with
106
+ the ruff linter and formatter; `just format` reformats and applies
107
+ auto-fixes in both.
108
+
109
+ - `just typecheck` runs tsc for the TypeScript packages and mypy for
110
+ Python.
111
+
112
+ - `just docs` builds the documentation with Sphinx into `docs/_build/html`
113
+ (the pages are MyST Markdown under `docs/`, and the manifest reference is
114
+ generated from the JSON schema on every build); `just docs-serve` rebuilds
115
+ on change; `just docs-clean` clears a stale build after structural changes
116
+ such as renamed or removed pages.
117
+
118
+ - `just pages` assembles the GitHub Pages site into `site/`: the landing
119
+ page from `github-pages/`, the JSON schemas under `schemas/v1alpha1/`,
120
+ and the example workshop as a JupyterLite site under `demo/`.
121
+
122
+ - `just clean` removes build outputs only. `just clean-examples` removes
123
+ what running the example workshops leaves behind (`_workshop` state,
124
+ `scratch` and `demo` directories) so a later run starts fresh.
125
+ `just distclean` does both and also removes `node_modules`, `.venv`,
126
+ caches, built docs and sites, returning the tree to a fresh checkout;
127
+ run `just install` afterwards.
128
+
129
+ ## Style
130
+
131
+ - Do not use emdashes in any files in this project. Rephrase with commas,
132
+ parentheses, colons, or separate sentences instead.
133
+
134
+ - In bulleted lists where items run to multiple lines, put a blank
135
+ line between the bullets: in docstrings, markdown files, and any
136
+ other prose. This is about the raw file being readable, not the
137
+ rendered form, which can look fine either way. Be consistent within
138
+ a list: if one item needs the spacing, space every item in that
139
+ list, never a mix.
140
+
141
+ - Python code must always use type hints. Add them to all function
142
+ and method signatures (parameters and return types), and to attributes
143
+ and variables where the type is not obvious from the assignment. When
144
+ adding or modifying code that lacks type hints, add them.
145
+
146
+ - TypeScript code must be explicitly typed at its boundaries: every
147
+ exported function, method, class member and module-level constant has
148
+ an explicit type or return type. Never use `any`; use `unknown` and
149
+ narrow it. Do not disable strict compiler options.
150
+
151
+ - Use vertical white space liberally inside function and method bodies,
152
+ in both Python and TypeScript. Write code in paragraphs: group the
153
+ statements that together perform one step, and separate each group
154
+ from the next with a blank line. Natural paragraph boundaries include
155
+ setup versus the main work versus the result, before and after a
156
+ conditional or loop, and around a with or try block. Do not cram a body
157
+ into one contiguous blob, and equally do not put a blank line between
158
+ every single statement; the blank lines should mark where one thought
159
+ ends and the next begins.
160
+
161
+ - Where it helps the reader, start a paragraph of code with a short
162
+ comment saying what that step does or why it is needed. Prefer one
163
+ comment per logical block over line-by-line commentary, and skip the
164
+ comment entirely when the code already says it plainly.
165
+
166
+ - Put a blank line between such a block comment and the code below it:
167
+ the comment introduces the paragraph rather than sitting flush against
168
+ its first line.
169
+
170
+ - Put a blank line between a function or method docstring and the first
171
+ line of code in the body.
172
+
173
+ - Every function, method or property that is part of the public API must
174
+ have a docstring (Python) or a TSDoc comment (TypeScript) saying what it
175
+ does. The exceptions are cases that are truly trivial and obvious, such
176
+ as an accessor property named for the attribute it returns, and dunder
177
+ methods implementing standard protocols.
178
+
179
+ - Every directive, action type or option added to the workshop format must
180
+ be reflected in the JSON schema, the lint rules and the format
181
+ documentation in the same change.
182
+
183
+ - Verify JupyterLab API names against the installed version's TypeScript
184
+ definitions (under `node_modules/@jupyterlab/*/lib/`) before using them.
185
+ Use only public tokens and APIs.
186
+
187
+ ## Git
188
+
189
+ - Git commit messages must never include a co-authored-by agent message or
190
+ any similar agent attribution trailer.
191
+
192
+ - An AI agent must never commit changes on its own initiative. Finish the
193
+ piece of work, summarize it, and wait to be told to commit. Permission to
194
+ commit applies only to the work it was given for; it does not carry
195
+ forward to later steps of a multi-step plan, each of which needs its own
196
+ review and its own instruction to commit. Uncommitted changes are how the
197
+ review happens: once work is committed it can no longer be reviewed as
198
+ the pending diff, so committing early makes review harder, not easier.
199
+
200
+ - `develop` is the default and working branch; `main` holds released
201
+ code and is what GitHub Pages and the Binder link build from. Never tag
202
+ from develop.
203
+
204
+ - A release bumps the version in four files together, in one commit on
205
+ develop: the root `package.json` (the Python package reads its version
206
+ from it), `packages/core/package.json`,
207
+ `packages/labextension/package.json`, and the
208
+ `jupyterlab-workshop==<version>` pin in `binder/requirements.txt`. The
209
+ pin matters because mybinder caches the image it builds for a commit of
210
+ main, so an unpinned install would freeze whichever release PyPI served
211
+ at the first launch. Then fast-forward main, wait for CI, and push the
212
+ bare version tag; the release workflow refuses a tag that does not
213
+ match `package.json`. Version numbers cannot be reused on PyPI, so a
214
+ failed publish means moving on to the next patch number.
215
+
216
+ - The release steps in full, for a release of `X.Y.Z`. Each step waits
217
+ for the previous one to finish; never chain the tag push behind a CI
218
+ check in one command, read the CI result first and then tag.
219
+
220
+ 1. On develop with a clean tree, set `"version": "X.Y.Z"` in the three
221
+ `package.json` files and `jupyterlab-workshop==X.Y.Z` in
222
+ `binder/requirements.txt`, then commit them together:
223
+
224
+ ```
225
+ git commit -am "Bump version to X.Y.Z"
226
+ ```
227
+
228
+ 2. Push develop and wait for its CI run to pass. A push starts the
229
+ `ci` workflow only:
230
+
231
+ ```
232
+ git push origin develop
233
+ gh run list --branch develop --workflow ci --limit 1
234
+ gh run watch <run-id> --exit-status
235
+ ```
236
+
237
+ 3. Fast-forward main to develop and push it. A push to main starts two
238
+ workflows, `ci` and `pages`, so select each run by workflow name
239
+ rather than taking the first id listed, and wait for both:
240
+
241
+ ```
242
+ git checkout main
243
+ git merge --ff-only develop
244
+ git push origin main
245
+ gh run list --branch main --workflow ci --limit 1
246
+ gh run list --branch main --workflow pages --limit 1
247
+ gh run watch <run-id> --exit-status
248
+ ```
249
+
250
+ 4. Only once both are green, push the bare version tag (no `v`
251
+ prefix) from main. This starts the `release` workflow: build, PyPI
252
+ publish and GitHub release:
253
+
254
+ ```
255
+ git tag X.Y.Z
256
+ git push origin X.Y.Z
257
+ gh run list --workflow release --limit 1
258
+ gh run watch <run-id> --exit-status
259
+ ```
260
+
261
+ 5. Confirm the release landed, then go back to develop:
262
+
263
+ ```
264
+ gh release view X.Y.Z
265
+ git checkout develop
266
+ ```
267
+
268
+ PyPI's JSON API lags a fresh upload; check the simple index at
269
+ `https://pypi.org/simple/jupyterlab-workshop/` instead. Locally,
270
+ `uv sync --reinstall-package jupyterlab-workshop` is needed before
271
+ the Python side reports the new version.
272
+
273
+ Three self-test flakes are known in CI and are not regressions: the
274
+ browser job's hello-jupyterlab hidden-kernel execute-capture step;
275
+ the lite job's terminal `execute` with `wait: prompt` timing out at
276
+ 120s on a trivial command; and the windows job failing only in
277
+ temporary directory cleanup, with `PermissionError: [WinError 32]`
278
+ on the `workshop-test-*` root because the server still holds it,
279
+ after the self-test itself has reported its passes. Rerun the failed
280
+ jobs with `gh run rerun <run-id> --failed` and wait for the rerun to
281
+ pass before going on; a rerun that fails again is a real problem, so
282
+ stop and report it. Read the result with
283
+ `gh run view <run-id> --json conclusion,jobs` rather than trusting
284
+ the exit status of `gh run watch`, which has reported success for a
285
+ failed run.
286
+
287
+ - When merging a feature branch back to main and pushing to the remote,
288
+ do not treat the work as landed until the CI workflow on GitHub has run
289
+ against the pushed merge and passed. Check the run (for example with
290
+ `gh run list --branch main` and `gh run watch`), and only once it is
291
+ green report that the changes are on the remote and clean up the feature
292
+ branch. If CI fails, leave the feature branch in place, report the
293
+ failure, and wait for instructions rather than deleting anything.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,128 @@
1
+ # Contributing
2
+
3
+ Development setup and workflow for jupyterlab-workshop. The user-facing
4
+ documentation is under `docs/` and published at
5
+ <https://jupyterlab-workshop.readthedocs.io/>; this file is about working
6
+ on the project itself.
7
+
8
+ ## Setup
9
+
10
+ Requires [uv](https://docs.astral.sh/uv/), [just](https://just.systems/),
11
+ Node.js and git.
12
+
13
+ ```
14
+ just install
15
+ just lab
16
+ ```
17
+
18
+ `just install` creates the Python environment, installs the JavaScript
19
+ workspace, builds the extension and links it into JupyterLab in
20
+ development mode. `just lab` starts JupyterLab with the repository root
21
+ as its root directory, which is where the example workshops live, and
22
+ with the settings in `dev/overrides.json`: it opens in the workshop
23
+ browser with the three examples listed as installed, the examples
24
+ collection under `collections/examples/` and the checkout's
25
+ `collections/catalog.json` subscribed to, so the collection and catalog
26
+ code paths have something local to work on. Open the Workshop panel in the
27
+ right sidebar (the `panelSide` setting, or dragging the tab, moves it to
28
+ the left).
29
+
30
+ To rebuild while editing the TypeScript, run `just watch` in a second
31
+ terminal and refresh the browser after each rebuild. `just --list` shows
32
+ every task.
33
+
34
+ ## Running the examples from a checkout
35
+
36
+ With `just lab` running, open Git from the command line from the
37
+ Installed section of the browser. The folder button in the panel header
38
+ picks any other workshop directory, and right-clicking a directory in
39
+ the file browser and choosing "Open as Workshop" does the same.
40
+
41
+ 1. On page one, click each command block in turn. The first click starts
42
+ a terminal named `git` in a split beneath the main area and runs the
43
+ command there. Every block shows exactly the text sent to the
44
+ terminal.
45
+
46
+ 2. On page two, the first action writes `README.md` into the `demo`
47
+ directory and opens it in the editor above the terminal. The final
48
+ action highlights the terminal with a short callout.
49
+
50
+ 3. Page three appends a line to the open file through the editor and
51
+ saves it, then opens a second terminal named `log` beside the first.
52
+
53
+ 4. Pages four and five branch, merge, create a conflict, open the file at
54
+ the conflict marker and resolve it by rewriting the file. The last
55
+ action shows a success notification.
56
+
57
+ 5. Use the arrows or the page dropdown to move between pages. Reload the
58
+ browser tab: the panel reopens on the same page.
59
+
60
+ `examples/hello-jupyterlab` exercises notebooks, kernels, the interface,
61
+ files, variables, tracks and automatic runs, and writes its files into
62
+ `examples/hello-jupyterlab/scratch`. The git example creates
63
+ `examples/git-basics/demo`, and every example keeps its progress under
64
+ its `_workshop` directory. All of these are ignored by git; the Restart
65
+ button in the panel header puts a workshop back, and `just
66
+ clean-examples` removes what every example left behind.
67
+
68
+ ## Layout
69
+
70
+ - `packages/core/` is `@jupyterlab-workshop/core`, pure TypeScript with no
71
+ JupyterLab dependencies: the workshop format, actions, variables, lint
72
+ rules and JSON schemas.
73
+
74
+ - `packages/labextension/` is `@jupyterlab-workshop/labextension`, the
75
+ JupyterLab frontend extension.
76
+
77
+ - `jupyterlab_workshop/` is the Python package: the `jupyter_server`
78
+ extension and the `jupyter workshop` command line.
79
+
80
+ - `examples/` holds the example workshops, which double as test fixtures.
81
+
82
+ - `docs/` is the Sphinx documentation and `github-pages/` the landing page
83
+ of the project site.
84
+
85
+ ## Checks and tests
86
+
87
+ ```
88
+ just lint
89
+ just typecheck
90
+ just test
91
+ ```
92
+
93
+ `just test` runs the Jest tests for `packages/core` and the pytest suite.
94
+ `just test-ui` runs the Galata browser tests against a real JupyterLab,
95
+ `just selftest` runs every action of the example workshops in JupyterLab,
96
+ and `just selftest-lite` does the same in a JupyterLite build. See
97
+ TESTING.md for where the tests live and how to add more.
98
+
99
+ Python is managed with uv and JavaScript with `jlpm`, the Yarn that ships
100
+ with JupyterLab, run as `uv run jlpm`. Do not use pip, npm or a system
101
+ yarn directly. AGENTS.md records the coding conventions the project
102
+ follows.
103
+
104
+ ## Documentation and the project site
105
+
106
+ `just docs` builds the documentation into `docs/_build/html`, and
107
+ `just docs-serve` rebuilds it on change. The manifest reference page is
108
+ generated from the JSON schema on every build. The screenshots under
109
+ `docs/_static` are committed; `just screenshots` retakes them from a
110
+ throwaway JupyterLab after an interface change, and needs the `test`
111
+ extra and a Chromium for Playwright.
112
+
113
+ `just pages` assembles the GitHub Pages site into `site/`: the landing
114
+ page, the JSON schemas and the example workshop built as a JupyterLite
115
+ site under `demo/`. Building the JupyterLite terminal needs `node`, `npm`
116
+ and `micromamba` on the path; pass `--no-terminal` to leave it out.
117
+
118
+ ## Releases
119
+
120
+ The version is read from the root `package.json` by the Python build, so
121
+ bump it there, in the two workspace `package.json` files, and in the
122
+ `jupyterlab-workshop==<version>` pin of `binder/requirements.txt`
123
+ together (mybinder caches the image built for a commit, so the pin keeps
124
+ the Binder image on the matching release).
125
+ Releases are made by pushing a tag that is the bare version string, such
126
+ as `0.1.0`, with no `v` prefix. The release workflow refuses to build if
127
+ the tag does not match the version in `package.json`, then builds the
128
+ wheel and sdist, attaches them to a GitHub release and publishes to PyPI.
@@ -0,0 +1,130 @@
1
+ # Development tasks for jupyterlab-workshop.
2
+ #
3
+ # Python is managed with uv, JavaScript with jlpm (the Yarn that ships with
4
+ # JupyterLab, installed into the project environment). Run `just --list`
5
+ # to see the available targets.
6
+
7
+ set positional-arguments
8
+
9
+ # List the available targets.
10
+ default:
11
+ @just --list
12
+
13
+ # Set up the development environment and link the extension into JupyterLab.
14
+ install:
15
+ uv sync --no-install-project
16
+ uv run --no-sync jlpm install
17
+ uv run --no-sync jlpm build
18
+ uv sync
19
+ uv run jupyter labextension develop . --overwrite
20
+ uv run jupyter server extension enable jupyterlab_workshop
21
+
22
+ # Build the TypeScript packages and the labextension bundle.
23
+ build:
24
+ uv run jlpm build
25
+
26
+ # Rebuild the TypeScript packages and labextension on change (run alongside `just lab`).
27
+ watch:
28
+ uv run jlpm watch
29
+
30
+ # Start JupyterLab from the repository root with the extension loaded.
31
+ # The settings in dev/overrides.json make it start in the workshop browser with the examples installed.
32
+ lab *args:
33
+ uv run jupyter lab --notebook-dir=. --LabApp.app_settings_dir=dev "$@"
34
+
35
+ # Run the fast test suites: Jest for packages/core and pytest for the Python package.
36
+ test *args:
37
+ uv run jlpm test
38
+ uv run pytest "$@"
39
+
40
+ # Run only the Jest tests for packages/core.
41
+ test-core *args:
42
+ uv run jlpm test "$@"
43
+
44
+ # Run only the pytest suite for the Python package.
45
+ test-python *args:
46
+ uv run pytest "$@"
47
+
48
+ # Run the Galata browser tests against a real JupyterLab on port 8890 (slow).
49
+ test-ui *args:
50
+ cd tests/ui-tests && uv run jlpm install && JUPYTER_PORT=8890 uv run jlpm playwright test "$@"
51
+
52
+ # Check TypeScript with eslint and prettier, and Python with ruff.
53
+ lint:
54
+ uv run jlpm lint:check
55
+ uv run ruff check .
56
+ uv run ruff format --check .
57
+
58
+ # Reformat and apply auto-fixes for TypeScript and Python.
59
+ format:
60
+ uv run jlpm lint
61
+ uv run ruff check --fix .
62
+ uv run ruff format .
63
+
64
+ # Type check the TypeScript packages with tsc and the Python package with mypy.
65
+ typecheck:
66
+ uv run jlpm typecheck
67
+ uv run mypy
68
+
69
+ # Retake the screenshots in docs/_static from a throwaway JupyterLab (needs the test extra and a Chromium for Playwright).
70
+ screenshots:
71
+ uv run python scripts/screenshots.py
72
+
73
+ # Build the documentation with Sphinx into docs/_build/html (generates the manifest reference first).
74
+ docs:
75
+ uv run sphinx-build -W --keep-going -b html docs docs/_build/html
76
+
77
+ # Serve the documentation with live reload.
78
+ docs-serve:
79
+ uv run sphinx-autobuild docs docs/_build/html
80
+
81
+ # Clear generated documentation outputs.
82
+ docs-clean:
83
+ rm -rf docs/_build docs/reference
84
+
85
+ # Assemble the GitHub Pages site into site/: landing page, JSON schemas and the JupyterLite demo.
86
+ pages *args:
87
+ rm -rf site
88
+ mkdir -p site/schemas/v1alpha1
89
+ cp github-pages/index.html site/index.html
90
+ touch site/.nojekyll
91
+ cp packages/core/src/schema/workshop.schema.json packages/core/src/schema/collection.schema.json packages/core/src/schema/catalog.schema.json site/schemas/v1alpha1/
92
+ uv run jupyter workshop lite examples/hello-jupyterlab --out site/demo "$@"
93
+
94
+ # Self-test a workshop directory in a real JupyterLab (default: every example).
95
+ selftest *args:
96
+ #!/usr/bin/env bash
97
+ set -euo pipefail
98
+ if [ "$#" -eq 0 ]; then set -- examples/git-basics examples/hello-jupyterlab examples/workshop-authoring; fi
99
+ for dir in "$@"; do uv run jupyter workshop test "$dir"; done
100
+
101
+ # Self-test a workshop in a static JupyterLite build (default: hello-jupyterlab).
102
+ selftest-lite *args:
103
+ #!/usr/bin/env bash
104
+ set -euo pipefail
105
+ if [ "$#" -eq 0 ]; then set -- examples/hello-jupyterlab; fi
106
+ for dir in "$@"; do uv run jupyter workshop test "$dir" --lite; done
107
+
108
+ # Build a JupyterLite site with the example workshop into lite-site/ and serve it.
109
+ lite *args:
110
+ uv run jupyter workshop lite examples/hello-jupyterlab --out lite-site --serve "$@"
111
+
112
+ # Remove build outputs (compiled TypeScript, the labextension bundle, lint caches).
113
+ clean:
114
+ uv run jlpm clean
115
+ uv run jlpm clean:lintcache
116
+
117
+ # Remove what running the example workshops leaves behind (_workshop state, scratch and demo directories).
118
+ clean-examples:
119
+ rm -rf examples/*/_workshop examples/*/scratch examples/*/demo
120
+
121
+ # Return to a fresh checkout: also removes node_modules, .venv, caches, built docs and sites.
122
+ distclean: clean-examples
123
+ rm -rf packages/core/lib packages/labextension/lib packages/*/tsconfig.tsbuildinfo
124
+ rm -rf jupyterlab_workshop/labextension jupyterlab_workshop/nodejs jupyterlab_workshop/schema
125
+ rm -rf .eslintcache .stylelintcache packages/core/coverage
126
+ rm -rf node_modules packages/*/node_modules tests/ui-tests/node_modules .venv
127
+ rm -rf site docs/_build docs/reference build dist lite-site .jupyterlite.doit.db .coverage htmlcov
128
+ rm -rf tests/ui-tests/test-results tests/ui-tests/playwright-report
129
+ rm -rf workshops
130
+ find . -type d \( -name __pycache__ -o -name .ipynb_checkpoints -o -name '*.egg-info' -o -name .yarn -o -name .mypy_cache -o -name .ruff_cache -o -name .pytest_cache \) -prune -exec rm -rf {} +