From 6a77d1766302940cbe024c682dbc9ee60a02f8fc Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 10 Jul 2026 16:09:51 +0200 Subject: [PATCH 1/3] Use plain text for reference solution (no ROT13) --- README.md | 2 +- src/index.ts | 4 ++-- src/utils.ts | 15 +++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 895e80e..0eea7b4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Clicking it opens a chat panel on the right side and sends the following context - The **cell source code** and its **kernel language** - Any **error output** produced by the cell (traceback included) - **Preceding code cells and markdown cell** (up to the previous markdown cell), used as the exercise description -- An optional **reference solution** and **evaluation criteria** stored in the cell metadata (the solution is ROT13-encoded to keep it hidden from students) +- An optional **reference solution** and **evaluation criteria** stored in the cell metadata The AI replies using a configurable **system prompt** (`TUTOR.md`) that shapes its pedagogical behavior. By default the extension looks for a `TUTOR.md` file walking up from the notebook's directory to the server root, so different courses or folders can each have their own tutor persona. diff --git a/src/index.ts b/src/index.ts index d291cb0..9640d83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ import { infoIcon } from '@jupyterlab/ui-components'; import { clearItem, stopItem } from './components'; import { TUTOR_USER, TutorChatModel } from './model'; -import { decodeRot13, isContinuous } from './utils'; +import { decodeSolution, isContinuous } from './utils'; const INFO_ICON_BASE_64 = btoa(infoIcon.svgstr); @@ -279,7 +279,7 @@ const plugin: JupyterFrontEndPlugin = { // Retrieve and decode reference_solution from metadata const rawSolution = cell.model.getMetadata('reference_solution'); const referenceSolution = - typeof rawSolution === 'string' ? decodeRot13(rawSolution) : ''; + typeof rawSolution === 'string' ? decodeSolution(rawSolution) : ''; // Retrieve evaluation_criteria from metadata const evaluationCriteria = cell.model.getMetadata( diff --git a/src/utils.ts b/src/utils.ts index d43ce6c..ac68c66 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,11 +13,14 @@ export function isContinuous(numbers: number[]): boolean { } /** - * Decodes a ROT13 encoded string. + * Decodes the reference solution. + * No-op for now, but could be a ROT13 decoder. */ -export function decodeRot13(str: string): string { - return str.replace(/[a-zA-Z]/g, c => { - const base = c <= 'Z' ? 65 : 97; - return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base); - }); +export function decodeSolution(str: string): string { + /* TODO: switch to a ROT13 encoded solution ? */ + // return str.replace(/[a-zA-Z]/g, c => { + // const base = c <= 'Z' ? 65 : 97; + // return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base); + // }); + return str; } From faf0857185955845971918d960d0c26115db1b6b Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 10 Jul 2026 16:26:01 +0200 Subject: [PATCH 2/3] Decode the reference solutions in the example Notebook --- examples/matplotlib_exercices.ipynb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/matplotlib_exercices.ipynb b/examples/matplotlib_exercices.ipynb index 373d8a1..2aff21d 100644 --- a/examples/matplotlib_exercices.ipynb +++ b/examples/matplotlib_exercices.ipynb @@ -49,9 +49,8 @@ "metadata": { "deletable": true, "editable": true, - "evaluation_criteria": "Typo Correction: Correct 'np.linsapce' typo to 'np.linspace'.\nPlot Output: Ensure the Sine and Cosine waves plot is correctly rendered without runtime errors." - , - "reference_solution": "vzcbeg zngcybgyvo.clcybg nf cyg\nvzcbeg ahzcl nf ac\nk = ac.yvafcnpr(0, 2 * ac.cv, 100)\nsvt, nk = cyg.fhocybgf(svtfvmr=(8, 4))\nnk.cybg(k, ac.fva(k), ynory='fva(k)', pbybe='eblnyoyhr')\nnk.cybg(k, ac.pbf(k), ynory='pbf(k)', pbybe='gbzngb', yvarfglyr='--')\nnk.frg_gvgyr('Fvar naq Pbfvar Jnirf')\nnk.frg_kynory('k')\nnk.frg_lynory('l')\nnk.yrtraq()\nnk.tevq(Gehr, nycun=0.3)\ncyg.gvtug_ynlbhg()\ncyg.fubj()", + "evaluation_criteria": "Typo Correction: Correct 'np.linsapce' typo to 'np.linspace'.\nPlot Output: Ensure the Sine and Cosine waves plot is correctly rendered without runtime errors.", + "reference_solution": "import matplotlib.pyplot as plt\nimport numpy as np\nx = np.linspace(0, 2 * np.pi, 100)\nfig, ax = plt.subplots(figsize=(8, 4))\nax.plot(x, np.sin(x), label='sin(x)', color='royalblue')\nax.plot(x, np.cos(x), label='cos(x)', color='tomato', linestyle='--')\nax.set_title('Sine and Cosine Waves')\nax.set_xlabel('x')\nax.set_ylabel('y')\nax.legend()\nax.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()", "slideshow": { "slide_type": "" }, @@ -100,7 +99,7 @@ "deletable": true, "editable": true, "evaluation_criteria": "Bar chart creation: Uses ax.bar with categories and values.\nAesthetics & Colors: Applies a different color to each bar.\nValue Labels: Correctly loops over the bars to add text scores above each bar.", - "reference_solution": "pngrtbevrf = ['Clguba', 'WninFpevcg', 'Ehfg', 'Tb', 'Whyvn']\ninyhrf = [85, 72, 58, 45, 30]\nsvt, nk = cyg.fhocybgf(svtfvmr=(8, 4))\npbybef = ['eblnyoyhr', 'gbzngb', 'zrqvhzfrnterra', 'benatr', 'zrqvhzchecyr']\nonef = nk.one(pngrtbevrf, inyhrf, pbybe=pbybef)\nnk.frg_gvgyr('Cebtenzzvat Ynathntr Fpberf')\nnk.frg_lynory('Fpber')\nsbe one va onef:\n liny = one.trg_urvtug()\n nk.grkg(one.trg_k() + one.trg_jvqgu()/2.0, liny + 1, fge(liny), un='pragre', in='obggbz')\ncyg.gvtug_ynlbhg()\ncyg.fubj()", + "reference_solution": "categories = ['Python', 'JavaScript', 'Rust', 'Go', 'Julia']\nvalues = [85, 72, 58, 45, 30]\nfig, ax = plt.subplots(figsize=(8, 4))\ncolors = ['royalblue', 'tomato', 'mediumseagreen', 'orange', 'mediumpurple']\nbars = ax.bar(categories, values, color=colors)\nax.set_title('Programming Language Scores')\nax.set_ylabel('Score')\nfor bar in bars:\n yval = bar.get_height()\n ax.text(bar.get_x() + bar.get_width()/2.0, yval + 1, str(yval), ha='center', va='bottom')\nplt.tight_layout()\nplt.show()", "slideshow": { "slide_type": "" }, @@ -158,7 +157,7 @@ "deletable": true, "editable": true, "evaluation_criteria": "Random data seed: Fails to set random seed 42, generating incorrect data coordinates.\nColorbar & Labels: Missing colorbar, title, grid, or x/y labels on the axes.\nScatter aesthetic parameters: Fails to set alpha=0.6, colormap='viridis', size mapping, or color mapping.", - "reference_solution": "ac.enaqbz.frrq(42)\na = 150\nk = ac.enaqbz.enaqa(a)\nl = ac.enaqbz.enaqa(a)\nfvmrf = ac.enaqbz.havsbez(20, 200, a)\npbybef = ac.enaqbz.enaq(a)\nsvt, nk = cyg.fhocybgf(svtfvmr=(8, 6))\nfpnggre = nk.fpnggre(k, l, p=pbybef, f=fvmrf, pznc='ivevqvf', nycun=0.6)\ncyg.pbybeone(fpnggre, nk=nk, ynory='Pbybe inyhr')\nnk.frg_gvgyr('Enaqbz Fpnggre Cybg')\nnk.frg_kynory('K inyhrf')\nnk.frg_lynory('L inyhrf')\nnk.tevq(Gehr, nycun=0.3)\ncyg.gvtug_ynlbhg()\ncyg.fubj()", + "reference_solution": "np.random.seed(42)\nn = 150\nx = np.random.randn(n)\ny = np.random.randn(n)\nsizes = np.random.uniform(20, 200, n)\ncolors = np.random.rand(n)\nfig, ax = plt.subplots(figsize=(8, 6))\nscatter = ax.scatter(x, y, c=colors, s=sizes, cmap='viridis', alpha=0.6)\nplt.colorbar(scatter, ax=ax, label='Color value')\nax.set_title('Random Scatter Plot')\nax.set_xlabel('X values')\nax.set_ylabel('Y values')\nax.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()", "slideshow": { "slide_type": "" }, From 785dff69895a2c7716f188bddefd6a0248789441 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 10 Jul 2026 16:29:34 +0200 Subject: [PATCH 3/3] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0eea7b4..a1d4af5 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Clicking it opens a chat panel on the right side and sends the following context - The **cell source code** and its **kernel language** - Any **error output** produced by the cell (traceback included) - **Preceding code cells and markdown cell** (up to the previous markdown cell), used as the exercise description -- An optional **reference solution** and **evaluation criteria** stored in the cell metadata +- An optional **reference_solution** (multiline string) and **evaluation_criteria** (multiline string) stored in the cell metadata The AI replies using a configurable **system prompt** (`TUTOR.md`) that shapes its pedagogical behavior. By default the extension looks for a `TUTOR.md` file walking up from the notebook's directory to the server root, so different courses or folders can each have their own tutor persona.