jupyterlab-markdown-syntax-rendering-fix 0.6.9__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.
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.claude/CLAUDE.md +59 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.claude/JOURNAL.md +14 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.copier-answers.yml +18 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.gitignore +133 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.prettierignore +8 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/.yarnrc.yml +2 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/CHANGELOG.md +15 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/CONTRIBUTING.md +80 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/LICENSE +29 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/Makefile +155 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/PKG-INFO +105 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/README.md +47 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/RELEASE.md +88 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/babel.config.js +1 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/docs/acc-crit-markdown-syntax-rendering-fix.md +48 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/eslint.config.mjs +68 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/install.json +5 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jest.config.js +28 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/__init__.py +16 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/_version.py +4 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/package.json +145 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/232.2e4cc97d2ebf5013.js +6 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/994.0763ed7064a0e1d0.js +1 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/remoteEntry.9bb7131db659374e.js +7 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/style.js +4 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/third-party-licenses.json +52 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/logs/README.md +7 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/package-lock.json +12229 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/package.json +140 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/pyproject.toml +83 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/setup.py +1 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/__tests__/jupyterlab_markdown_syntax_rendering_fix.spec.ts +100 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/highlight.ts +67 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/index.ts +175 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/base.css +5 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/index.css +1 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/index.js +1 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/tsconfig.json +25 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/tsconfig.test.json +3 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/README.md +167 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/jupyter_server_test_config.py +12 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/package.json +15 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/playwright.config.js +14 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/tests/jupyterlab_markdown_syntax_rendering_fix.spec.ts +71 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/yarn.lock +5525 -0
- jupyterlab_markdown_syntax_rendering_fix-0.6.9/yarn.lock +10342 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- @import /home/lab/.claude/CLAUDE.md -->
|
|
2
|
+
|
|
3
|
+
# Project-Specific Configuration
|
|
4
|
+
|
|
5
|
+
This file imports workspace-level configuration from `/home/lab/.claude/CLAUDE.md` (user home).
|
|
6
|
+
All workspace rules apply. Project-specific rules below strengthen or extend them.
|
|
7
|
+
|
|
8
|
+
The `/home/lab/.claude/` directory contains additional instruction files and skills referenced
|
|
9
|
+
by that CLAUDE.md. Consult it to discover all applicable standards.
|
|
10
|
+
|
|
11
|
+
## Mandatory Bans (Reinforced)
|
|
12
|
+
|
|
13
|
+
The following workspace rules are STRICTLY ENFORCED for this project:
|
|
14
|
+
|
|
15
|
+
- **No automatic git tags** - only create tags when user explicitly requests
|
|
16
|
+
- **No automatic version changes** - only modify version in package.json/pyproject.toml/etc. when user explicitly requests
|
|
17
|
+
- **No automatic publishing** - never run `make publish`, `npm publish`, `twine upload`, or similar without explicit user request
|
|
18
|
+
- **No manual package installs if Makefile exists** - use `make install` or equivalent Makefile targets, not direct `pip install`/`uv install`/`npm install`
|
|
19
|
+
- **No automatic git commits or pushes** - only when user explicitly requests
|
|
20
|
+
|
|
21
|
+
## Project Context
|
|
22
|
+
|
|
23
|
+
JupyterLab 4 frontend extension (`kind: frontend`, copier template `jupyterlab/extension-template` v4.6.2).
|
|
24
|
+
Single TypeScript plugin at `src/index.ts`, no Python server component.
|
|
25
|
+
|
|
26
|
+
**Purpose**: Fix the intermittent loss of syntax highlighting in rendered Markdown fenced code blocks.
|
|
27
|
+
|
|
28
|
+
**Root cause** (reconstructed from the shipped JupyterLab 4.6 `jlab_core` bundle): `marked` runs in async
|
|
29
|
+
mode. An async highlighter (`g`) writes a `lang|text` cache by awaiting `registry.highlight(...)`, which lazily
|
|
30
|
+
dynamic-imports the CodeMirror language chunk. The synchronous code renderer reads that cache and, on any miss,
|
|
31
|
+
falls back to a plain uncoloured `<pre><code>`. A miss occurs whenever the async highlight threw - the language
|
|
32
|
+
chunk's lazy `spec.load()` import rejected (chunk-load flake/timeout, often behind the JupyterHub proxy) or the
|
|
33
|
+
language registry was not yet wired when an early render fired. The `catch` swallows the error
|
|
34
|
+
(`console.error("Failed to highlight ...")`), the cache stays empty, and the renderer ships plain text. The
|
|
35
|
+
failure is independent of mermaid - it reproduces on bash/python/json fences with zero mermaid present.
|
|
36
|
+
|
|
37
|
+
**Implication for the fix**: the durable fix is to retry or re-run the highlight after the language chunk loads;
|
|
38
|
+
the only built-in workaround is re-rendering (reload tab / reopen preview) once chunks are warm.
|
|
39
|
+
|
|
40
|
+
## Journal Rules (Project-Specific)
|
|
41
|
+
|
|
42
|
+
- **APPEND ONLY**: New journal entries MUST be appended at the end of the file, never inserted between existing entries
|
|
43
|
+
- Entries maintain strict chronological order by position - the last entry in the file is always the most recent work
|
|
44
|
+
- Never reorder, move, or insert entries out of sequence
|
|
45
|
+
- The Stellars **journal plugin** is the canonical tool for this file: create via `/journal:create`, append via `/journal:update`, archive via `/journal:archive`. The `journal:journal` skill auto-triggers on any mention of "journal" and runs `journal-tools check` after every write
|
|
46
|
+
- Direct edits to `JOURNAL.md` are a last resort - prefer the plugin so modus secundis format, continuous numbering and append-only order are enforced automatically
|
|
47
|
+
|
|
48
|
+
## Required Workspace Skills
|
|
49
|
+
|
|
50
|
+
These skills MUST be consulted when working on this project (located at `/home/lab/.claude/skills/`, invocable by name):
|
|
51
|
+
|
|
52
|
+
- **jupyterlab-extension** (`/home/lab/.claude/skills/jupyterlab-extension/SKILL.md`) - extension development guidelines, testing strategy, jupyter-releaser CI/CD workflows, common caveats, TypeScript compatibility, syntax highlighting
|
|
53
|
+
- **playwright** (`/home/lab/.claude/skills/playwright/SKILL.md`) - browser automation for screenshots and UI verification, serving local files, connecting to authenticated JupyterHub
|
|
54
|
+
|
|
55
|
+
## Strengthened Rules
|
|
56
|
+
|
|
57
|
+
- **Install with `make install`** - all package install/build goes through the Makefile (`make install`, `make build`, `make test`). Never run `pip install`, `jlpm install`, `jlpm build`, `npm install` directly while a Makefile target exists
|
|
58
|
+
- **Always track `package.json` and `package-lock.json`** - both files are committed artefacts; never gitignore or omit `package-lock.json`, and stage both whenever dependencies change
|
|
59
|
+
- **Keep the Makefile current** - before any build/release work, compare the version header of the local `Makefile` (line 1, currently `version 1.32`) against the canonical `/home/lab/workspace/private/jupyterlab/@utils/jupyterlab-extensions/Makefile`. As soon as the canonical file is a newer version, replace the local `Makefile` with it
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Claude Code Journal
|
|
2
|
+
|
|
3
|
+
This journal tracks substantive work on documents, diagrams, and documentation content.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
1. **Task - Project initialization** (v0.1.0): Created `jupyterlab_markdown_syntax_rendering_fix` as a new JupyterLab 4 frontend extension and ran `/init-project`<br>
|
|
8
|
+
**Result**: Scaffolded from the `jupyterlab/extension-template` copier template (v4.6.2, `kind: frontend`) - single TypeScript plugin at `src/index.ts`, no server component. Replaced the inline `.claude/CLAUDE.md` with an `@import` of the user-home workspace config (`/home/lab/.claude/CLAUDE.md`) plus project-specific sections: Mandatory Bans, Project Context (the Markdown highlighting root cause), Journal Rules, a Required Workspace Skills section pointing at `jupyterlab-extension` and `playwright`, and strengthened rules mandating `make install`, tracking both `package.json` and `package-lock.json`, and syncing the local `Makefile` against the canonical `@utils/jupyterlab-extensions/Makefile` (currently v1.32). Rewrote `README.md` with the full KOLOMOLO/PyPI/npm badge set, a brief feature statement, and Install/Uninstall only. Initialized the git repository with `git init -b main` and committed the initial import.
|
|
9
|
+
|
|
10
|
+
2. **Task [Extended] - Implement Markdown highlight recovery** (v0.1.7): Implemented the actual fix - recover syntax highlighting lost when JupyterLab's async highlighter throws and the markdown renderer ships a plain `pre > code.language-*`<br>
|
|
11
|
+
**Result**: Confirmed the root cause live via headless Chrome + the Playwright Python API (token from env, never leaked - the MCP path was unusable because its sandbox has no env and it echoes code; noted the working recipe in the `playwright` skill): a highlighted block carries token `<span>`s (`childElementCount > 0`), a failed one is bare text. Split the pure DOM detection (`languageFromClass`, `needsHighlight`, `collectPlainBlocks`) into `src/highlight.ts` and the JupyterLab-coupled plugin into `src/index.ts`, depending on `IEditorLanguageRegistry`. A `MutationObserver` on `app.shell.node` (skipping `.cm-editor` and `code[data-msrf]` subtrees to cut churn) finds plain blocks and `rehighlight()` re-runs `languages.highlight(text, findBest(lang), host)`, committing via `replaceChildren` only when `host.textContent === text` (no truncation). A four-state `data-msrf` marker (`1`/`plain`/`skipped`/`failed`) + a `WeakSet` dedupe make it idempotent; only a thrown highlight is retried, backing off 750/1500/3000 ms - verified against `@jupyterlab/codemirror` source that `getLanguage` caches `spec.support` only on success, so retries genuinely re-import. Build needed `typescript ~5.8` (lib0) and `chalk`/`webpack` pins in both `resolutions` and `overrides` (Node 24 isolated build). Survived five adversarial-review rounds (R1/R2 found single-shot abandonment + over-broad observer; R3 the retry-theatre claim, disproven by source; R4 and R5 returned SHIP with guard-hardening applied). Added jest unit tests (13 pass) and a Galata UI test that injects a plain block and asserts recovery (run on a port-8899 server since 8888 is the live hub); wrote `docs/acc-crit-markdown-syntax-rendering-fix.md` (22 criteria, all met). Lint clean, `labextension list` OK.
|
|
12
|
+
|
|
13
|
+
3. **Task - Release v0.6.9 to npm + PyPI** (v0.6.9): First public release of the Markdown highlight-recovery extension to both registries via `make publish`<br>
|
|
14
|
+
**Result**: Greened GitHub Actions first. `check_release` failed because `package.json` still carried the copier placeholder `repository.url` (`.git`) - set `repository.url`/`homepage`/`bugs.url` to the real `stellarshenson/jupyterlab_markdown_syntax_rendering_fix` URLs so `jupyter-releaser check-npm` matches the cloned repo. `Check Links` 404'd on the pre-publish `pepy.tech` download badges - added `ignore_links` (npm/pepy patterns) to the check-links step. All five jobs (build, test_isolated, integration tests, Check Links, check_release) then passed. Added `CHANGELOG.md` (Keep a Changelog) and a README "How it works" section. `make publish` auto-incremented 0.6.8 -> 0.6.9, built the wheel + sdist and pushed to npm (`npm publish --access public`) and PyPI (`twine upload`).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2
|
+
_commit: v4.6.2
|
|
3
|
+
_src_path: https://github.com/jupyterlab/extension-template
|
|
4
|
+
advanced: false
|
|
5
|
+
author_email: konrad.jelen+github@gmail.com
|
|
6
|
+
author_name: Stellars Henson
|
|
7
|
+
has_ai_rules: false
|
|
8
|
+
has_binder: false
|
|
9
|
+
has_settings: false
|
|
10
|
+
kind: frontend
|
|
11
|
+
labextension_name: jupyterlab_markdown_syntax_rendering_fix
|
|
12
|
+
project_short_description: Jupyterlab extension to fix a common issue with Markdown
|
|
13
|
+
renderer where some race condition causes for the fenced code block to not have
|
|
14
|
+
proper syntax highlighting
|
|
15
|
+
python_name: jupyterlab_markdown_syntax_rendering_fix
|
|
16
|
+
repository: ''
|
|
17
|
+
test: true
|
|
18
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
*.bundle.*
|
|
2
|
+
lib/
|
|
3
|
+
node_modules/
|
|
4
|
+
*.log
|
|
5
|
+
.eslintcache
|
|
6
|
+
.stylelintcache
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.ipynb_checkpoints
|
|
9
|
+
*.tsbuildinfo
|
|
10
|
+
jupyterlab_markdown_syntax_rendering_fix/labextension
|
|
11
|
+
# Version file is handled by hatchling
|
|
12
|
+
jupyterlab_markdown_syntax_rendering_fix/_version.py
|
|
13
|
+
|
|
14
|
+
# Integration tests
|
|
15
|
+
ui-tests/test-results/
|
|
16
|
+
ui-tests/playwright-report/
|
|
17
|
+
|
|
18
|
+
# Local dev build artifacts
|
|
19
|
+
.nodeenv/
|
|
20
|
+
.playwright-mcp/
|
|
21
|
+
junit.xml
|
|
22
|
+
|
|
23
|
+
# Created by https://www.gitignore.io/api/python
|
|
24
|
+
# Edit at https://www.gitignore.io/?templates=python
|
|
25
|
+
|
|
26
|
+
### Python ###
|
|
27
|
+
# Virtual environments
|
|
28
|
+
.venv
|
|
29
|
+
|
|
30
|
+
# Byte-compiled / optimized / DLL files
|
|
31
|
+
__pycache__/
|
|
32
|
+
*.py[cod]
|
|
33
|
+
*$py.class
|
|
34
|
+
|
|
35
|
+
# C extensions
|
|
36
|
+
*.so
|
|
37
|
+
|
|
38
|
+
# Distribution / packaging
|
|
39
|
+
.Python
|
|
40
|
+
build/
|
|
41
|
+
develop-eggs/
|
|
42
|
+
dist/
|
|
43
|
+
downloads/
|
|
44
|
+
eggs/
|
|
45
|
+
.eggs/
|
|
46
|
+
lib/
|
|
47
|
+
lib64/
|
|
48
|
+
parts/
|
|
49
|
+
sdist/
|
|
50
|
+
var/
|
|
51
|
+
wheels/
|
|
52
|
+
pip-wheel-metadata/
|
|
53
|
+
share/python-wheels/
|
|
54
|
+
.installed.cfg
|
|
55
|
+
*.egg
|
|
56
|
+
MANIFEST
|
|
57
|
+
|
|
58
|
+
# PyInstaller
|
|
59
|
+
# Usually these files are written by a python script from a template
|
|
60
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
61
|
+
*.manifest
|
|
62
|
+
*.spec
|
|
63
|
+
|
|
64
|
+
# Installer logs
|
|
65
|
+
pip-log.txt
|
|
66
|
+
pip-delete-this-directory.txt
|
|
67
|
+
|
|
68
|
+
# Unit test / coverage reports
|
|
69
|
+
htmlcov/
|
|
70
|
+
.tox/
|
|
71
|
+
.nox/
|
|
72
|
+
.coverage
|
|
73
|
+
.coverage.*
|
|
74
|
+
.cache
|
|
75
|
+
nosetests.xml
|
|
76
|
+
coverage/
|
|
77
|
+
coverage.xml
|
|
78
|
+
*.cover
|
|
79
|
+
.hypothesis/
|
|
80
|
+
.pytest_cache/
|
|
81
|
+
|
|
82
|
+
# Translations
|
|
83
|
+
*.mo
|
|
84
|
+
*.pot
|
|
85
|
+
|
|
86
|
+
# Scrapy stuff:
|
|
87
|
+
.scrapy
|
|
88
|
+
|
|
89
|
+
# Sphinx documentation
|
|
90
|
+
docs/_build/
|
|
91
|
+
|
|
92
|
+
# PyBuilder
|
|
93
|
+
target/
|
|
94
|
+
|
|
95
|
+
# pyenv
|
|
96
|
+
.python-version
|
|
97
|
+
|
|
98
|
+
# celery beat schedule file
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Spyder project settings
|
|
105
|
+
.spyderproject
|
|
106
|
+
.spyproject
|
|
107
|
+
|
|
108
|
+
# Rope project settings
|
|
109
|
+
.ropeproject
|
|
110
|
+
|
|
111
|
+
# Mr Developer
|
|
112
|
+
.mr.developer.cfg
|
|
113
|
+
.project
|
|
114
|
+
.pydevproject
|
|
115
|
+
|
|
116
|
+
# mkdocs documentation
|
|
117
|
+
/site
|
|
118
|
+
|
|
119
|
+
# mypy
|
|
120
|
+
.mypy_cache/
|
|
121
|
+
.dmypy.json
|
|
122
|
+
dmypy.json
|
|
123
|
+
|
|
124
|
+
# Pyre type checker
|
|
125
|
+
.pyre/
|
|
126
|
+
|
|
127
|
+
# End of https://www.gitignore.io/api/python
|
|
128
|
+
|
|
129
|
+
# OSX files
|
|
130
|
+
.DS_Store
|
|
131
|
+
|
|
132
|
+
# Yarn cache
|
|
133
|
+
.yarn/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here, following the Keep a Changelog format and semantic versioning.
|
|
4
|
+
|
|
5
|
+
<!-- <START NEW CHANGELOG ENTRY> -->
|
|
6
|
+
|
|
7
|
+
## [0.6.9] - 2026-06-23
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Recover syntax highlighting on rendered Markdown fenced code blocks that render plain when JupyterLab's async highlighter fails - a cold or flaky CodeMirror language-chunk import, or a language registry not yet wired when an early render fires
|
|
12
|
+
- `MutationObserver`-based detection over the application shell, re-running the highlight through `IEditorLanguageRegistry` with a bounded, backed-off retry; idempotent per block and content-safe (spans are swapped in only when the highlighted text round-trips exactly, so it never truncates)
|
|
13
|
+
- jest unit tests for the detection helpers and a Galata integration test covering activation and recovery
|
|
14
|
+
|
|
15
|
+
<!-- <END NEW CHANGELOG ENTRY> -->
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development install
|
|
4
|
+
|
|
5
|
+
Note: You will need Node.js to build the extension package.
|
|
6
|
+
You may install it from [nodejs.org](https://nodejs.org/en/download). We
|
|
7
|
+
recommend using the latest LTS version of Node.js.
|
|
8
|
+
|
|
9
|
+
The `jlpm` command is JupyterLab's pinned version of
|
|
10
|
+
[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
|
|
11
|
+
`yarn` or `npm` in lieu of `jlpm` below.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Clone the repo to your local environment
|
|
15
|
+
# Change directory to the jupyterlab_markdown_syntax_rendering_fix directory
|
|
16
|
+
|
|
17
|
+
# Set up a virtual environment and install package in development mode
|
|
18
|
+
python -m venv .venv
|
|
19
|
+
source .venv/bin/activate
|
|
20
|
+
pip install --editable "."
|
|
21
|
+
|
|
22
|
+
# Link your development version of the extension with JupyterLab
|
|
23
|
+
jupyter-builder develop . --overwrite
|
|
24
|
+
|
|
25
|
+
# Rebuild extension Typescript source after making changes
|
|
26
|
+
# IMPORTANT: Unlike the steps above which are performed only once, do this step
|
|
27
|
+
# every time you make a change.
|
|
28
|
+
jlpm build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Watch the source directory in one terminal, automatically rebuilding when needed
|
|
35
|
+
jlpm watch
|
|
36
|
+
# Run JupyterLab in another terminal
|
|
37
|
+
jupyter lab
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).
|
|
41
|
+
|
|
42
|
+
By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
jupyter lab build --minimize=False
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development uninstall
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip uninstall jupyterlab_markdown_syntax_rendering_fix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
In development mode, you will also need to remove the symlink created by `jupyter-builder develop`
|
|
55
|
+
command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
|
|
56
|
+
folder is located. Then you can remove the symlink named `jupyterlab_markdown_syntax_rendering_fix` within that folder.
|
|
57
|
+
|
|
58
|
+
## Testing the extension
|
|
59
|
+
|
|
60
|
+
#### Frontend tests
|
|
61
|
+
|
|
62
|
+
This extension is using [Jest](https://jestjs.io/) for JavaScript code testing.
|
|
63
|
+
|
|
64
|
+
To execute them, execute:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
jlpm
|
|
68
|
+
jlpm test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Integration tests
|
|
72
|
+
|
|
73
|
+
This extension uses [Playwright](https://playwright.dev/docs/intro) for the integration tests (aka user level tests).
|
|
74
|
+
More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab.
|
|
75
|
+
|
|
76
|
+
More information is provided within the [ui-tests](./ui-tests/README.md) README.
|
|
77
|
+
|
|
78
|
+
## Packaging the extension
|
|
79
|
+
|
|
80
|
+
See [RELEASE](RELEASE.md)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Stellars Henson
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Makefile for Jupyterlab extensions version 1.32
|
|
2
|
+
# changelog:
|
|
3
|
+
# 1.32 - use a project-local nodeenv at .nodeenv/ instead of overwriting the python
|
|
4
|
+
# prefix via `nodeenv -p` (which used to fail with "Text file busy" when the
|
|
5
|
+
# existing node binary was held open). PATH=.nodeenv/bin:$PATH is exported so
|
|
6
|
+
# every target transparently picks up the pinned local node + npm + yarn.
|
|
7
|
+
# install_dependencies now guards each install step - only what's missing
|
|
8
|
+
# gets installed. mrproper removes .nodeenv too.
|
|
9
|
+
# 1.31 - mrproper now removes ui-tests/node_modules (Playwright browser binaries)
|
|
10
|
+
# 1.30 - check twine in check_dependencies, ensure publish doesn't fail on missing twine
|
|
11
|
+
# 1.29 - replace yarn with jlpm, add prettier format, auto-commit and push after publish
|
|
12
|
+
# 1.28 - initial versioned Makefile
|
|
13
|
+
# author: Stellars Henson <konrad.jelen@gmail.com>
|
|
14
|
+
# License: MIT Open Source License
|
|
15
|
+
|
|
16
|
+
.PHONY: build install clean uninstall publish dependencies mrproper increment_version install_dependencies check_dependencies upgrade help test
|
|
17
|
+
.DEFAULT_GOAL := help
|
|
18
|
+
|
|
19
|
+
# Project-local node environment - keeps node/npm/yarn pinned per project and out of
|
|
20
|
+
# the python prefix. Created by `install_dependencies` and torn down by `mrproper`.
|
|
21
|
+
NODEENV := $(CURDIR)/.nodeenv
|
|
22
|
+
export PATH := $(NODEENV)/bin:$(PATH)
|
|
23
|
+
|
|
24
|
+
# Read current version from package.json (only if node is available)
|
|
25
|
+
VERSION := $(shell command -v node >/dev/null 2>&1 && node -p "require('./package.json').version" || echo "0.0.0")
|
|
26
|
+
|
|
27
|
+
## increment project version
|
|
28
|
+
increment_version:
|
|
29
|
+
@echo "Current version: $(VERSION)"
|
|
30
|
+
@bash -c 'CURRENT_VERSION=$(VERSION); \
|
|
31
|
+
IFS="." read -r major minor patch <<< "$$CURRENT_VERSION"; \
|
|
32
|
+
NEW_PATCH=$$((patch + 1)); \
|
|
33
|
+
NEW_VERSION="$$major.$$minor.$$NEW_PATCH"; \
|
|
34
|
+
echo "New version: $$NEW_VERSION"; \
|
|
35
|
+
sed -i "s/\"version\": \"$$CURRENT_VERSION\"/\"version\": \"$$NEW_VERSION\"/" package.json; '
|
|
36
|
+
|
|
37
|
+
## build packages
|
|
38
|
+
build: clean increment_version check_dependencies
|
|
39
|
+
npm install
|
|
40
|
+
jlpm install
|
|
41
|
+
npx prettier --write package-lock.json package.json
|
|
42
|
+
python -m build
|
|
43
|
+
|
|
44
|
+
## install package
|
|
45
|
+
install: build
|
|
46
|
+
pip install dist/*.whl --force-reinstall
|
|
47
|
+
|
|
48
|
+
## run tests
|
|
49
|
+
test: check_dependencies
|
|
50
|
+
jlpm test
|
|
51
|
+
|
|
52
|
+
## clean builds and installables
|
|
53
|
+
clean: uninstall check_dependencies
|
|
54
|
+
@command -v npm >/dev/null 2>&1 && npm run clean || true
|
|
55
|
+
@command -v npm >/dev/null 2>&1 && npm run clean:labextension || true
|
|
56
|
+
rm -rf dist lib || true
|
|
57
|
+
|
|
58
|
+
## uninstall package
|
|
59
|
+
uninstall: check_dependencies
|
|
60
|
+
pip uninstall -y dist/*.whl 2>/dev/null || true
|
|
61
|
+
|
|
62
|
+
## check if required dependencies are installed in the project-local nodeenv
|
|
63
|
+
check_dependencies:
|
|
64
|
+
@echo "Checking dependencies..."
|
|
65
|
+
@MISSING=""; \
|
|
66
|
+
[ -x "$(NODEENV)/bin/node" ] || MISSING="$$MISSING node"; \
|
|
67
|
+
[ -x "$(NODEENV)/bin/npm" ] || MISSING="$$MISSING npm"; \
|
|
68
|
+
[ -x "$(NODEENV)/bin/yarn" ] || MISSING="$$MISSING yarn"; \
|
|
69
|
+
python -m twine --version >/dev/null 2>&1 || MISSING="$$MISSING twine"; \
|
|
70
|
+
if [ -n "$$MISSING" ]; then \
|
|
71
|
+
echo "Missing dependencies:$$MISSING"; \
|
|
72
|
+
echo "Installing missing dependencies..."; \
|
|
73
|
+
$(MAKE) install_dependencies; \
|
|
74
|
+
else \
|
|
75
|
+
echo "All dependencies are installed."; \
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
## publish package to public repository
|
|
79
|
+
publish: check_dependencies install
|
|
80
|
+
npm publish --access public
|
|
81
|
+
python -m twine upload dist/*
|
|
82
|
+
git add package.json package-lock.json
|
|
83
|
+
git commit -m "chore: post-publish $$(node -p "require('./package.json').version") package metadata"
|
|
84
|
+
git push
|
|
85
|
+
|
|
86
|
+
## install required build dependencies into the project-local nodeenv (only what's missing)
|
|
87
|
+
install_dependencies:
|
|
88
|
+
@if ! python -m twine --version >/dev/null 2>&1; then \
|
|
89
|
+
echo "Installing twine..."; \
|
|
90
|
+
pip install twine; \
|
|
91
|
+
fi
|
|
92
|
+
@if [ ! -x "$(NODEENV)/bin/node" ] || [ ! -x "$(NODEENV)/bin/npm" ]; then \
|
|
93
|
+
echo "Creating project-local node environment at $(NODEENV)..."; \
|
|
94
|
+
python -c "import nodeenv" >/dev/null 2>&1 || pip install nodeenv; \
|
|
95
|
+
nodeenv --node=lts --prebuilt "$(NODEENV)"; \
|
|
96
|
+
fi
|
|
97
|
+
@if [ ! -x "$(NODEENV)/bin/yarn" ]; then \
|
|
98
|
+
echo "Installing yarn + rimraf into $(NODEENV)..."; \
|
|
99
|
+
"$(NODEENV)/bin/npm" install -g yarn rimraf; \
|
|
100
|
+
fi
|
|
101
|
+
@echo "node: $$($(NODEENV)/bin/node --version 2>/dev/null) ($(NODEENV)/bin/node)"
|
|
102
|
+
@echo "npm: $$($(NODEENV)/bin/npm --version 2>/dev/null)"
|
|
103
|
+
@echo "yarn: $$($(NODEENV)/bin/yarn --version 2>/dev/null)"
|
|
104
|
+
|
|
105
|
+
## upgrade all npm and yarn dependencies
|
|
106
|
+
upgrade: check_dependencies
|
|
107
|
+
jlpm up
|
|
108
|
+
|
|
109
|
+
## cleanup all build and metabuild artefacts (including the project-local nodeenv)
|
|
110
|
+
mrproper: clean uninstall
|
|
111
|
+
rm -rf node_modules .yarn ui-tests/node_modules .nodeenv || true
|
|
112
|
+
|
|
113
|
+
## prints the list of available commands
|
|
114
|
+
help:
|
|
115
|
+
@echo ""
|
|
116
|
+
@echo "$$(tput bold)Available rules:$$(tput sgr0)"
|
|
117
|
+
@sed -n -e "/^## / { \
|
|
118
|
+
h; \
|
|
119
|
+
s/.*//; \
|
|
120
|
+
:doc" \
|
|
121
|
+
-e "H; \
|
|
122
|
+
n; \
|
|
123
|
+
s/^## //; \
|
|
124
|
+
t doc" \
|
|
125
|
+
-e "s/:.*//; \
|
|
126
|
+
G; \
|
|
127
|
+
s/\\n## /---/; \
|
|
128
|
+
s/\\n/ /g; \
|
|
129
|
+
p; \
|
|
130
|
+
}" ${MAKEFILE_LIST} \
|
|
131
|
+
| LC_ALL='C' sort --ignore-case \
|
|
132
|
+
| awk -F '---' \
|
|
133
|
+
-v ncol=$$(tput cols) \
|
|
134
|
+
-v indent=19 \
|
|
135
|
+
-v col_on="$$(tput setaf 6)" \
|
|
136
|
+
-v col_off="$$(tput sgr0)" \
|
|
137
|
+
'{ \
|
|
138
|
+
printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
|
|
139
|
+
n = split($$2, words, " "); \
|
|
140
|
+
line_length = ncol - indent; \
|
|
141
|
+
for (i = 1; i <= n; i++) { \
|
|
142
|
+
line_length -= length(words[i]) + 1; \
|
|
143
|
+
if (line_length <= 0) { \
|
|
144
|
+
line_length = ncol - indent - length(words[i]) - 1; \
|
|
145
|
+
printf "\n%*s ", -indent, " "; \
|
|
146
|
+
} \
|
|
147
|
+
printf "%s ", words[i]; \
|
|
148
|
+
} \
|
|
149
|
+
printf "\n"; \
|
|
150
|
+
}'
|
|
151
|
+
@echo ""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# EOF
|
|
155
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupyterlab_markdown_syntax_rendering_fix
|
|
3
|
+
Version: 0.6.9
|
|
4
|
+
Summary: Jupyterlab extension to fix a common issue with Markdown renderer where some race condition causes for the fenced code block to not have proper syntax highlighting
|
|
5
|
+
Project-URL: Homepage, https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix.git
|
|
8
|
+
Author-email: Stellars Henson <konrad.jelen+github@gmail.com>
|
|
9
|
+
License: BSD 3-Clause License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026, Stellars Henson
|
|
12
|
+
All rights reserved.
|
|
13
|
+
|
|
14
|
+
Redistribution and use in source and binary forms, with or without
|
|
15
|
+
modification, are permitted provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
18
|
+
list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
25
|
+
contributors may be used to endorse or promote products derived from
|
|
26
|
+
this software without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
31
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
32
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
33
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
34
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
35
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
36
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
37
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Keywords: jupyter,jupyterlab,jupyterlab-extension
|
|
40
|
+
Classifier: Framework :: Jupyter
|
|
41
|
+
Classifier: Framework :: Jupyter :: JupyterLab
|
|
42
|
+
Classifier: Framework :: Jupyter :: JupyterLab :: 4
|
|
43
|
+
Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
|
|
44
|
+
Classifier: Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt
|
|
45
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
46
|
+
Classifier: Programming Language :: Python
|
|
47
|
+
Classifier: Programming Language :: Python :: 3
|
|
48
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
49
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
50
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
51
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
52
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
53
|
+
Requires-Python: >=3.10
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: jupyter-builder>=1.0.0; extra == 'dev'
|
|
56
|
+
Requires-Dist: jupyterlab>=4; extra == 'dev'
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
# jupyterlab_markdown_syntax_rendering_fix
|
|
60
|
+
|
|
61
|
+
[](https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix/actions/workflows/build.yml)
|
|
62
|
+
[](https://www.npmjs.com/package/jupyterlab_markdown_syntax_rendering_fix)
|
|
63
|
+
[](https://pypi.org/project/jupyterlab-markdown-syntax-rendering-fix/)
|
|
64
|
+
[](https://pepy.tech/project/jupyterlab-markdown-syntax-rendering-fix)
|
|
65
|
+
[](https://jupyterlab.readthedocs.io/en/stable/)
|
|
66
|
+
[](https://kolomolo.com)
|
|
67
|
+
[](https://www.paypal.com/donate/?hosted_button_id=B4KPBJDLLXTSA)
|
|
68
|
+
|
|
69
|
+
Fenced code blocks in rendered Markdown sometimes appear plain and uncoloured - the highlighting silently fails when a CodeMirror language chunk loads late or the language registry is not yet ready, and JupyterLab falls back to plain text. This extension restores the highlighting that was lost to that race.
|
|
70
|
+
|
|
71
|
+
## Features
|
|
72
|
+
|
|
73
|
+
- **Recovers lost highlighting** - re-applies syntax highlighting to fenced code blocks that rendered plain because the async highlighter missed the cache
|
|
74
|
+
- **Language agnostic** - works for any fenced language (bash, python, json, ...), independent of mermaid
|
|
75
|
+
- **Targets the cold-load race** - handles the case where a language chunk imports late or the registry is wired after an early render
|
|
76
|
+
- **Frontend only** - pure TypeScript labextension, no server component
|
|
77
|
+
|
|
78
|
+
## How it works
|
|
79
|
+
|
|
80
|
+
JupyterLab highlights fenced code in rendered Markdown with an async pass that fills a cache and a synchronous renderer that reads it. When the async highlight throws - a CodeMirror language-chunk import that rejects, or a registry that is not yet wired when an early render fires - the cache misses and the renderer emits a plain `<pre><code>`. This extension watches the application shell for those plain blocks and re-runs the highlight once the language is available.
|
|
81
|
+
|
|
82
|
+
- **Detect** - a `MutationObserver` flags any rendered `pre > code` that has a `language-*` class and text but no token `<span>` children
|
|
83
|
+
- **Recover** - re-runs the highlight through `IEditorLanguageRegistry` and swaps in the token spans, only when the highlighted text matches the source exactly so it never truncates content
|
|
84
|
+
- **Resilient** - retries a thrown highlight a few times with backoff while the language chunk finishes loading, then gives up cleanly and leaves the original plain text untouched
|
|
85
|
+
- **Unobtrusive** - each block is handled at most once, and editor and overlay churn is skipped to keep the observer cheap
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
- JupyterLab >= 4.0.0
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
To install the extension, execute:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install jupyterlab_markdown_syntax_rendering_fix
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Uninstall
|
|
100
|
+
|
|
101
|
+
To remove the extension, execute:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip uninstall jupyterlab_markdown_syntax_rendering_fix
|
|
105
|
+
```
|