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.
Files changed (46) hide show
  1. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.claude/CLAUDE.md +59 -0
  2. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.claude/JOURNAL.md +14 -0
  3. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.copier-answers.yml +18 -0
  4. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.gitignore +133 -0
  5. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.prettierignore +8 -0
  6. jupyterlab_markdown_syntax_rendering_fix-0.6.9/.yarnrc.yml +2 -0
  7. jupyterlab_markdown_syntax_rendering_fix-0.6.9/CHANGELOG.md +15 -0
  8. jupyterlab_markdown_syntax_rendering_fix-0.6.9/CONTRIBUTING.md +80 -0
  9. jupyterlab_markdown_syntax_rendering_fix-0.6.9/LICENSE +29 -0
  10. jupyterlab_markdown_syntax_rendering_fix-0.6.9/Makefile +155 -0
  11. jupyterlab_markdown_syntax_rendering_fix-0.6.9/PKG-INFO +105 -0
  12. jupyterlab_markdown_syntax_rendering_fix-0.6.9/README.md +47 -0
  13. jupyterlab_markdown_syntax_rendering_fix-0.6.9/RELEASE.md +88 -0
  14. jupyterlab_markdown_syntax_rendering_fix-0.6.9/babel.config.js +1 -0
  15. jupyterlab_markdown_syntax_rendering_fix-0.6.9/docs/acc-crit-markdown-syntax-rendering-fix.md +48 -0
  16. jupyterlab_markdown_syntax_rendering_fix-0.6.9/eslint.config.mjs +68 -0
  17. jupyterlab_markdown_syntax_rendering_fix-0.6.9/install.json +5 -0
  18. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jest.config.js +28 -0
  19. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/__init__.py +16 -0
  20. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/_version.py +4 -0
  21. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/package.json +145 -0
  22. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/232.2e4cc97d2ebf5013.js +6 -0
  23. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/994.0763ed7064a0e1d0.js +1 -0
  24. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/remoteEntry.9bb7131db659374e.js +7 -0
  25. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/style.js +4 -0
  26. jupyterlab_markdown_syntax_rendering_fix-0.6.9/jupyterlab_markdown_syntax_rendering_fix/labextension/static/third-party-licenses.json +52 -0
  27. jupyterlab_markdown_syntax_rendering_fix-0.6.9/logs/README.md +7 -0
  28. jupyterlab_markdown_syntax_rendering_fix-0.6.9/package-lock.json +12229 -0
  29. jupyterlab_markdown_syntax_rendering_fix-0.6.9/package.json +140 -0
  30. jupyterlab_markdown_syntax_rendering_fix-0.6.9/pyproject.toml +83 -0
  31. jupyterlab_markdown_syntax_rendering_fix-0.6.9/setup.py +1 -0
  32. jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/__tests__/jupyterlab_markdown_syntax_rendering_fix.spec.ts +100 -0
  33. jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/highlight.ts +67 -0
  34. jupyterlab_markdown_syntax_rendering_fix-0.6.9/src/index.ts +175 -0
  35. jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/base.css +5 -0
  36. jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/index.css +1 -0
  37. jupyterlab_markdown_syntax_rendering_fix-0.6.9/style/index.js +1 -0
  38. jupyterlab_markdown_syntax_rendering_fix-0.6.9/tsconfig.json +25 -0
  39. jupyterlab_markdown_syntax_rendering_fix-0.6.9/tsconfig.test.json +3 -0
  40. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/README.md +167 -0
  41. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/jupyter_server_test_config.py +12 -0
  42. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/package.json +15 -0
  43. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/playwright.config.js +14 -0
  44. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/tests/jupyterlab_markdown_syntax_rendering_fix.spec.ts +71 -0
  45. jupyterlab_markdown_syntax_rendering_fix-0.6.9/ui-tests/yarn.lock +5525 -0
  46. 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,8 @@
1
+ node_modules
2
+ **/node_modules
3
+ **/lib
4
+ **/package.json
5
+ !/package.json
6
+ jupyterlab_markdown_syntax_rendering_fix
7
+ eslint.config.mjs
8
+ .venv
@@ -0,0 +1,2 @@
1
+ nodeLinker: node-modules
2
+ enableScripts: false
@@ -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
+ [![GitHub Actions](https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix/actions/workflows/build.yml/badge.svg)](https://github.com/stellarshenson/jupyterlab_markdown_syntax_rendering_fix/actions/workflows/build.yml)
62
+ [![npm version](https://img.shields.io/npm/v/jupyterlab_markdown_syntax_rendering_fix.svg)](https://www.npmjs.com/package/jupyterlab_markdown_syntax_rendering_fix)
63
+ [![PyPI version](https://img.shields.io/pypi/v/jupyterlab-markdown-syntax-rendering-fix.svg)](https://pypi.org/project/jupyterlab-markdown-syntax-rendering-fix/)
64
+ [![Total PyPI downloads](https://static.pepy.tech/badge/jupyterlab-markdown-syntax-rendering-fix)](https://pepy.tech/project/jupyterlab-markdown-syntax-rendering-fix)
65
+ [![JupyterLab 4](https://img.shields.io/badge/JupyterLab-4-orange.svg)](https://jupyterlab.readthedocs.io/en/stable/)
66
+ [![Brought To You By KOLOMOLO](https://img.shields.io/badge/Brought%20To%20You%20By-KOLOMOLO-00ffff?style=flat)](https://kolomolo.com)
67
+ [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-blue?style=flat)](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
+ ```