-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat(compute): per-lecture live compute via an enable_live_compute site option #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7e857fa
a6e7828
3831312
17a87f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * Per-lecture live compute (#114). | ||
| * | ||
| * `project.thebe` turns in-page compute on for a whole project, but Pyodide | ||
| * cannot run every lecture (numba and JAX do not import), so the `enable_live_compute` | ||
| * site option gates the control per page. Resolution, most specific first: | ||
| * | ||
| * 1. `site.enable_live_compute` in the page's frontmatter | ||
| * 2. `site.options.enable_live_compute` in myst.yml (the project-wide default) | ||
| * 3. `true` -- absent means today's behaviour: compute wherever `project.thebe` | ||
| * is set, so existing projects change nothing, and a series can adopt the | ||
| * flag incrementally by marking its known-incompatible lectures `false`. | ||
| * A series that would rather certify lectures one at a time sets the | ||
| * site-wide value to `false` and opts pages in. | ||
| * | ||
| * A gated page loses the whole compute surface, not just the toolbar toggle: | ||
| * Page.tsx turns the resolved value into the `optionOverrideFn` of | ||
| * `ComputeOptionsProvider`, which flips `compute.enabled` and with it the | ||
| * toolbar slot, the error tray and the execute scope in one place. "Not | ||
| * compatible" means nothing on the page should try to run. | ||
| * | ||
| * Pure TypeScript, no React, so tests/unit/live-compute.test.mjs runs it under | ||
| * `node --test` with type stripping like the other helpers. | ||
| */ | ||
|
|
||
| /** Reads a boolean option leniently: YAML booleans, or the strings a hand edit produces. */ | ||
| function asBoolean(value: unknown): boolean | undefined { | ||
| if (typeof value === 'boolean') return value; | ||
| if (typeof value === 'string') { | ||
| const v = value.trim().toLowerCase(); | ||
| if (v === 'true' || v === 'yes' || v === 'on') return true; | ||
| if (v === 'false' || v === 'no' || v === 'off') return false; | ||
| } | ||
|
Comment on lines
+27
to
+33
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, non-blocking: the Either trim Generated by Claude Code |
||
| return undefined; | ||
| } | ||
|
|
||
| export function resolveLiveCompute( | ||
| pageOptions: Record<string, unknown> | undefined, | ||
| siteOptions: Record<string, unknown> | undefined, | ||
| ): boolean { | ||
| return asBoolean(pageOptions?.enable_live_compute) ?? asBoolean(siteOptions?.enable_live_compute) ?? true; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /** | ||
| * Unit tests for the per-lecture live-compute resolver (app/liveCompute.ts, | ||
| * #114): page value over site value over an enabled default. Run with | ||
| * `npm run test:unit` (node --test with type stripping, Node >= 23.6). | ||
| */ | ||
| import assert from 'node:assert/strict'; | ||
| import { test } from 'node:test'; | ||
|
|
||
| import { resolveLiveCompute } from '../../app/liveCompute.ts'; | ||
|
|
||
| test('absent everywhere: enabled (today\'s behaviour, project.thebe decides)', () => { | ||
| assert.equal(resolveLiveCompute(undefined, undefined), true); | ||
| assert.equal(resolveLiveCompute({}, {}), true); | ||
| }); | ||
|
|
||
| test('site-wide false turns the default off; a page opts back in', () => { | ||
| assert.equal(resolveLiveCompute({}, { enable_live_compute: false }), false); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: true }, { enable_live_compute: false }), true); | ||
| }); | ||
|
|
||
| test('page false wins over an enabled site (incremental adoption)', () => { | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: false }, {}), false); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: false }, { enable_live_compute: true }), false); | ||
| }); | ||
|
|
||
| test('a page that sets other site keys but not this one inherits the site value', () => { | ||
| assert.equal(resolveLiveCompute({ hide_search: true }, { enable_live_compute: false }), false); | ||
| assert.equal(resolveLiveCompute({ hide_search: true }, {}), true); | ||
| }); | ||
|
|
||
| test('string spellings from hand edits are read; garbage falls through', () => { | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: 'false' }, {}), false); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: 'no' }, {}), false); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: 'TRUE' }, { enable_live_compute: false }), true); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: 'maybe' }, { enable_live_compute: false }), false); | ||
| assert.equal(resolveLiveCompute({ enable_live_compute: 'maybe' }, {}), true); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| { | ||
| "cells": [ | ||
| { | ||
| "cell_type": "markdown", | ||
| "metadata": {}, | ||
| "source": "# Notebook opted into compute\n\nThis page exercises **notebook cell output rendering** — the area changed by the\n`@myst-theme` v1.0.0 output-node AST change. Outputs are baked in (no execution).\n\nThis page sets `site: {enable_live_compute: true}` in its notebook metadata, overriding the site-wide `false` (#114).\n" | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 1, | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "name": "stdout", | ||
| "output_type": "stream", | ||
| "text": [ | ||
| "hello from a stream output\n" | ||
| ] | ||
| } | ||
| ], | ||
| "source": [ | ||
| "print(\"hello from a stream output\")" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 2, | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "data": { | ||
| "text/plain": [ | ||
| " value\n", | ||
| "count 3.0\n", | ||
| "mean 2.0\n", | ||
| "std 1.0" | ||
| ] | ||
| }, | ||
| "execution_count": 2, | ||
| "metadata": {}, | ||
| "output_type": "execute_result" | ||
| } | ||
| ], | ||
| "source": [ | ||
| "summary # a plain-text execute_result" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 3, | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "ename": "ValueError", | ||
| "evalue": "a deliberate error to render the traceback", | ||
| "output_type": "error", | ||
| "traceback": [ | ||
| "Traceback (most recent call last):", | ||
| "ValueError: a deliberate error to render the traceback" | ||
| ] | ||
| } | ||
| ], | ||
| "source": [ | ||
| "raise ValueError(\"a deliberate error to render the traceback\")" | ||
| ] | ||
| } | ||
| ], | ||
| "metadata": { | ||
| "kernelspec": { | ||
| "display_name": "Python 3", | ||
| "language": "python", | ||
| "name": "python3" | ||
| }, | ||
| "language_info": { | ||
| "name": "python", | ||
| "version": "3.11" | ||
| }, | ||
| "site": { | ||
| "enable_live_compute": true | ||
| } | ||
| }, | ||
| "nbformat": 4, | ||
| "nbformat_minor": 5 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, non-blocking: both
as anycasts go away ifresolveLiveComputetakesPartial<TemplateOptions> | undefined(or{ enable_live_compute?: unknown } | undefined) instead ofRecord<string, unknown>.TemplateOptionshas no index signature, which is the only reason the cast is needed. No behaviour change, andasBooleanalready acceptsunknown.Generated by Claude Code