Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Worker messages:
- **Visualizer modes:** in `box` mode every call is a `Frame` box at its call site. In `substitution` mode (the default; prop `visualizerMode`, embed `?visualizer=`), a call with `shortForm` renders as `Frame` scope `'inline'`: its return expression with parameters substituted, in parentheses in operator context.
- **Layout and scrolling:** nested boxes show their variables below their code (CSS). The scroll effect follows the active box horizontally. It sets `scrollLeft`/`scrollTop` on the visualizer view only; don't use `scrollIntoView`, which also scrolls the LiaScript page around the embed and moves the step buttons.
- **Console and Stop:** output, REPL inputs, Tests reports and notes form one transcript (`ConsoleEntry` list after `output`). While Python is busy for more than `STOP_BUTTON_DELAY`, the run button is **Stop**: `stopPython` terminates the worker and calls `startWorker` again, so the REPL namespace is gone after a stop. The embed reports this to its parent (protocol version 4, `glpython:busy`/`glpython:stop`).
- **One Monaco model for all files:** a tab switch replaces the editor's text. `updateCode` ignores flush events (`setValue`), since `@monaco-editor/react` shows a read-only file (`ProjectFile.readOnly`) with `setValue` before its `onChange` knows the new tab - the text would land in the previous file.
- **UI strings** come in via the `t: CoreStrings` prop. `src/i18n.ts` is their single source: `coreStrings` holds German and English, and `resolveUiLocale` picks the language. The embed uses them directly; the glpython IDE imports them and adds only its shell strings. New strings have been added as *optional* `CoreStrings` keys with an English fallback, so older hosts keep compiling; add both languages to `coreStrings`. Python values, reprs and error messages are never translated.
- **Two layouts:** with `compact` (the embed) the original tree (`main-split` > `editor-column` + graphics aside), unchanged and styled by `src/embed/style.css`; without it (the IDE) the stacked layout (`workspace-stack`: `stack-top` with the editor panel and, while drawing, the graphics, then `stack-divider` and `stack-console`). Both are assembled from the same JSX pieces (`actionsNode`, `visualizerBarNode`, `editorWrapNode`, `consoleNode`, …) just before the return. The buttons can be portaled into the host's bar (`actionsContainer`).
- **Embedded/reduced layouts:** the knobs are `compact`, `capabilities` (`WorkspaceCapabilities`, including `repl`) and `command` (`WorkspaceCommand`, an immutable `{id, type}` used by hosts to trigger run/visualize/test/stop/reset without the workspace's own buttons; `reset` restarts Python and clears the console). Defaults must preserve the full-workspace behavior.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,13 @@ the embed's Reset does. The run button keeps its width when it becomes
the editor. `capabilities={{ closeFileTabs: false }}` drops the ✕ on the file
tabs, for a host that shows every file of the project as a tab.

A file with `readOnly: true` (`ProjectFile`) is one to read, not to change,
e.g. a course's library that learners use as a black box: the editor shows it
read-only (on an iPad the keyboard doesn't open), its tab has a lock
(`readOnlyFile`, also the message when typing into it), and
`updateProjectFile`, `renameProjectFile` and `deleteProjectFile` leave it
as it is. It runs, imports and visualizes like any other file.

### Compact embeds

`GlPythonWorkspace` accepts optional `compact` and `capabilities` props. The
Expand Down
20 changes: 20 additions & 0 deletions scripts/check-embed-protocol.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
GLPYTHON_MAX_SOURCE_LENGTH,
parseParentMessage,
parseParentOrigin,
deleteProjectFile,
renameProjectFile,
updateProjectFile,
} from '../dist/index.js'

assert.equal(parseParentOrigin('https://course.example'), 'https://course.example')
Expand Down Expand Up @@ -131,4 +134,21 @@ const readyMessage = {
}
assert.equal(readyMessage.capabilities.input, false)

// Not the protocol, but the one check against the built library: a
// read-only file (a course's library) is never changed, renamed or deleted by
// the project helpers; an ordinary file still is.
const project = {
id: 'p', name: 'p', mainFile: 'auswerter.py',
files: {
'parser.py': { path: 'parser.py', label: 'parser', kind: 'python', code: 'def parse(text): ...', readOnly: true },
'auswerter.py': { path: 'auswerter.py', label: 'auswerter', kind: 'python', code: 'from parser import parse' },
},
}
assert.equal(updateProjectFile(project, 'parser.py', 'kaputt'), project)
assert.equal(renameProjectFile(project, 'parser.py', 'p2.py'), project)
assert.equal(deleteProjectFile(project, 'parser.py'), project)
assert.equal(updateProjectFile(project, 'auswerter.py', 'x = 1').files['auswerter.py'].code, 'x = 1')
assert.equal(updateProjectFile(project, 'auswerter.py', 'x = 1').files['parser.py'].readOnly, true)
assert.deepEqual(Object.keys(renameProjectFile(project, 'auswerter.py', 'main.py').files), ['parser.py', 'main.py'])

console.log('Embed protocol checks passed.')
Loading
Loading