Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
288ce74
Split into a workspace with a React package and a web component
claude Aug 5, 2026
031e1c9
Move the diagram into the store, add undo/redo, shortcuts and German
claude Aug 5, 2026
10dafa8
Upgrade dependencies to latest, and fix restoring older shared links
claude Aug 5, 2026
e0c760b
Model String as a reference type, collapse it in the view instead
claude Aug 5, 2026
f532548
Make a diagram a sequence of steps
claude Aug 5, 2026
855fa79
Split the playground into a student's and a teacher's
claude Aug 6, 2026
0e03f93
Mark what each step changed, and make the call stack behave like one
claude Aug 6, 2026
68db131
Exercises, garbage prediction, whole-trace export, presets
claude Aug 6, 2026
2cce643
Float the node palette over the canvas as a panel
claude Aug 6, 2026
d13e682
Fix exported images losing every reference arrow
claude Aug 6, 2026
771b33d
Define classes by pasting Java source
claude Aug 6, 2026
f26e570
Say what applying a set of classes will cost
claude Aug 6, 2026
e96357f
Treat a field nobody has typed into as empty
claude Aug 6, 2026
bbfe2a2
Pack the canvas more tightly
claude Aug 6, 2026
2d76eb5
Stop the collector button from covering the step bar
claude Aug 6, 2026
9c4fd43
Make the bottom bar one card, not two of different heights
claude Aug 6, 2026
57e7313
Keep the floating panels clear of each other at every width
claude Aug 6, 2026
a167e4c
Translate React Flow's own accessible text
claude Aug 6, 2026
84267ab
Add a VS Code extension for .jmp files
claude Aug 6, 2026
c6687fe
Put the project's own copyright on the licence
claude Aug 6, 2026
07c946e
Ship the licence with the published packages
claude Aug 6, 2026
2d63e17
Publish the VS Code extension from CI
claude Aug 6, 2026
90bed88
Stop a number field counting on its own, and let the config scroll
mikebarkmin Aug 9, 2026
eb5506d
Build before typechecking in CI
mikebarkmin Aug 9, 2026
682356a
Move the workspace to pnpm 11
mikebarkmin Aug 9, 2026
7935d4e
Keep pnpm 9 as the floor, for Vercel
mikebarkmin Aug 9, 2026
e087866
Add a help button, and the documentation it opens
mikebarkmin Aug 9, 2026
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
15 changes: 15 additions & 0 deletions .changeset/afraid-bats-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Split the playground into a student's and a teacher's.

Configuring classes and authoring the steps of a trace are the teacher's work, and having them on screen while a student works through a diagram is noise. They now live in their own component and their own custom element, following how learningmap separates its viewer from its editor.

- `MemoryPlayground` and `<java-memory-playground>` are the student's: the whole diagram, every edit, and the steps of a trace to walk through.
- `MemoryPlaygroundEditor` and `<java-memory-playground-editor>` add class configuration and step authoring on top.
- The standalone app serves the student's playground, and the teacher's at `?edit` (or `/edit` where a rewrite rule exists).

The split is about which tools are on screen, not about what a student is allowed to touch: a student still builds objects, connects references, walks the steps and runs the garbage collector. The configuration route is closed in the student's playground rather than merely hidden, so the keyboard shortcut cannot reach it either.
18 changes: 18 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
"access": "public",
"baseBranch": "main",
"changelog": [
"@changesets/changelog-github",
{
"repo": "openpatch/java-memory-playground"
}
],
"privatePackages": {
"version": true,
"tag": true
},
"commit": false,
"ignore": [],
"updateInternalDependencies": "patch"
}
9 changes: 9 additions & 0 deletions .changeset/eighty-rules-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@openpatch/java-memory-playground": patch
---

Add `onEdit`, which fires on every edit rather than only on Save.

`onChange` says the user considers the diagram finished. A host that owns the file and has a save of its own — an editor with a dirty marker — needs the other signal: that something changed just now. Loading a `memory` prop is deliberately not an edit, so opening a diagram does not mark it as changed, and neither is panning or zooming, though the viewport is written along with the next real edit.

React Flow writes measurements back as it mounts, which replaces the steps without changing the diagram, so an edit is only reported when what would be written to a file actually differs.
11 changes: 11 additions & 0 deletions .changeset/fifty-pugs-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Upgrade to React 19, pako 3, vitest 4 and TypeScript 7.

The React peer range still covers 18 and 19. Internally the store moved off zustand's legacy `zustand/traditional` entry to `useStore` + `useShallow`, which is the recommended zustand 5 API.

Vite stays on 7 deliberately: Vite 8 bundles with Rolldown, which leaves an unresolved `require("react")` in the CJS shim that `@xyflow/react` pulls in through zustand 4, producing a bundle that throws on load.
7 changes: 7 additions & 0 deletions .changeset/great-moons-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@openpatch/java-memory-playground": patch
---

Translate React Flow's own accessible text.

React Flow ships its ARIA descriptions and control labels in English, so a German playground announced "Zoom In", "Fit View" and "Press enter or space to select a node" beside its own translated labels. They go through `ariaLabelConfig` now and follow the playground's language like everything else.
9 changes: 9 additions & 0 deletions .changeset/great-pears-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@openpatch/java-memory-playground": patch
---

Fix exported images losing every reference arrow.

An exported diagram had its objects and frames but none of the arrows between them, which is most of what a memory diagram says. Exporting deep-clones the edge SVG and drops anything a stylesheet contributed, so the stroke our CSS supplied never made it into the picture and the paths came out invisible. Edges carry their stroke inline now.

The capture also frames the whole diagram before photographing it and crops to the nodes, so nothing scrolled out of view is missing and the empty canvas is gone, and it keeps the arrowheads, whose SVG markers live outside the viewport.
14 changes: 14 additions & 0 deletions .changeset/khaki-jars-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Mark what each step changed, and make the call stack behave like one.

Walking a trace only helps if you can see what moved, so a step is now marked against the one before it: a green outline for what appeared, a dashed amber one for what changed, and an amber reference for one that was assigned or repointed. The first step marks nothing, because nothing has happened yet. `hideStepChanges` turns it off, and `diffSteps` is exported for the same comparison elsewhere.

Two fixes to the stack itself:

- Only the frame on top can return. Returning from the middle is the one thing a stack cannot do, so the button on the other frames is disabled and says why rather than disappearing. Returning now also removes the references that frame held, which is what leaves an object unreachable for the garbage collector to find.
- A new frame takes an index one past the deepest frame. Counting the frames instead handed out an index a surviving frame already had, as soon as one in the middle was gone.
8 changes: 8 additions & 0 deletions .changeset/lucky-jars-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@openpatch/java-memory-playground": patch
"@openpatch/java-memory-playground-web-component": patch
---

Ship the licence with the packages.

Both declared `"license": "MIT"` but published only `dist`, so neither tarball carried the licence text it was pointing at.
13 changes: 13 additions & 0 deletions .changeset/olive-books-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@openpatch/java-memory-playground": patch
---

Say what applying a set of classes will cost before it happens.

Classes belong to the whole diagram, so saving them reaches every step — and pasting a new file over the old one can quietly delete what the objects were holding. Save now names it first: the fields that go and how many objects lose a value or a reference with them, and the objects whose class is gone, which stay in the diagram but can never be made again.

It only asks when there is something to lose. Adding a field, or changing classes no object uses, still saves without a dialog, because a warning that always appears is one nobody reads. A field nobody has typed into counts as empty even though an `int` shows a `0` and a `boolean` shows a box — that is what the field starts out holding, not something to lose.

`defaultValueFor` says what a field of a given type starts out holding. New objects and new arrays were each deciding that for themselves, in the same way, in two places.

A removed reference field now takes its edge with it. The attribute was deleted but the edge stayed behind, drawn from a handle that no longer existed, and the reference came back if the field ever did.
19 changes: 19 additions & 0 deletions .changeset/olive-cups-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@openpatch/java-memory-playground": patch
---

Stop the garbage collector button from covering the step bar.

The collector was pinned bottom-right and the step bar bottom-centre, as separate panels that could not see each other. Below about 1000px of canvas they slid into each other, and the collector sat on top of "Add step" and "Delete step" — at 768px "Delete step" could not be clicked at all.

They share one row now, so they cannot overlap at any width: side by side when there is room, the collector on its own line when there is not.

The row also gets the width it is entitled to. React Flow centres a bottom-centre panel with `left: 50%`, which caps how wide it can shrink-to-fit at half the canvas, so the row was wrapping with the whole right half of the screen still empty.

The step bar itself wraps as a last resort rather than pushing its own buttons off the edge of a narrow screen.

The bottom bar is one card rather than two. Side by side, the step controls and the collector were two cards of different heights nudged together by a 4px gap. Every control in the row is the same height now, and the collector wears the colour the diagram uses for garbage, which is what tells it apart from the step controls next to it.

`.button-gc` never applied. It is one class, and `.java-memory-playground button` is a class plus a type, so the collector had been taking the default button background all along.

The bottom row stops short of the zoom controls in the corner below it, and the toolbar stops short of the palette across from it, wrapping onto a second line instead of sliding underneath. Every floating overlay is now clear of every other one from 1400px down to 380px of canvas.
14 changes: 14 additions & 0 deletions .changeset/olive-eels-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Exercises, garbage prediction, whole-trace export, and presets for teachers.

- **Exercises.** A step can be marked as one: the teacher authors it as the answer, and a student's playground starts them from the step before it and checks what they build. The check compares the shape reachable from each named root rather than addresses, so a student's own objects match a solution built with different ones, and the report names the variable that is wrong.
- **Garbage prediction.** With `gcPrediction` on, the collector asks first — the student marks what they think is unreachable and is scored before the sweep.
- **Download all steps.** One image with every step under its label, which is what a worksheet needs; exporting gave you only the step on screen.
- **Presets.** `optionPresets` names the option combinations a course moves through — references only, with the stack, everything — as buttons in the config view.

Also fixes a crash: the reachability walk behind the garbage collector and the stack fading followed references without remembering where it had been, so any reference cycle between two or more objects overflowed the stack and took the whole playground down. A circular linked list did it. It tracks visited nodes now, and an unreachable cycle is collected as it should be.
13 changes: 13 additions & 0 deletions .changeset/olive-hounds-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Model String as the reference type it is, and make collapsing it a display choice.

String was listed among the primitive types, so a String value was stored inside the object that held it and drawn as a field of that object. That is the shape of the misconception behind `==` versus `.equals()`, and because it lived in the data model rather than the view, the saved diagram could not express a String reference at all.

A String value is now a heap object like any other, and the new `inlineStrings` option — on by default — decides whether it is drawn as its own box or shown inside its owner. Default diagrams look exactly as before: a diagram about a linked list does not sprout a box per name. Turning the option off draws the String objects, which is what makes two references to one String, and therefore the string pool, teachable at all.

Diagrams saved with inline String values are converted when read, so existing links keep working. Surrounding quotes, which used to be typed into the value by hand and were therefore inconsistent between hand-authored and student-created diagrams, are no longer stored — they are rendered.
29 changes: 29 additions & 0 deletions .changeset/olive-planes-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"java-memory-playground-studio": patch
"web": minor
---

Add a help button, and the documentation it opens.

The toolbar in the top right corner gains a `?` that opens
[the documentation](https://jmp.openpatch.org/documentation.html), a single
static page served with the app. It covers reading a diagram and building one,
values against references, steps and traces, the garbage collector, the
configuration view and every option, the keyboard shortcuts, sharing, embedding,
and `.jmp` files.

It explains the `?edit` URL, which nothing until now did: that the app is two
playgrounds, that appending `?edit` is what turns the student's into the
teacher's, and that because the diagram lives in the fragment and the mode does
not, the same picture has both an editing link and a working link.

The button is a link rather than a button so that whatever owns navigation
around the playground opens it the way it opens any other link — a new tab in a
browser, the external browser from a VS Code webview. `window.open` is blocked
in some of those hosts. The URL is absolute for the same reason: an embedded
playground would resolve a relative one against a page that is not the app.

`return` on a stack frame is no longer translated. It is the keyword the student
would write, and it is spelled the same in a German lesson as in an English one.
13 changes: 13 additions & 0 deletions .changeset/olive-pugs-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Keep the diagram in the store, and add undo/redo, keyboard shortcuts and German.

- Nodes and edges moved out of React Flow's local state into the playground store, so an edit is never lost by switching to the config view or reloading. Save is now a commit that fires `change`, not the only thing that records your work.
- Undo/redo via zundo, with toolbar buttons and `Ctrl/Cmd+Z` / `Ctrl/Cmd+Y`. Only diagram edits are undoable, and one drag is one step.
- Keyboard shortcuts for save, undo, redo, config and zoom, overridable through `keyBindings`.
- English and German translations, selected with the new `language` prop/attribute or the browser language.
- URL persistence now syncs continuously, throttled and via `history.replaceState`, so it no longer fills up the back button.
13 changes: 13 additions & 0 deletions .changeset/plain-owls-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@openpatch/java-memory-playground": patch
---

Define the classes of a diagram by pasting Java source.

A teacher already has the classes written down — in a worksheet, in an IDE, on a slide — so the config view now takes them as Java instead of asking for each field through a dialog. Only the structure is read: class names, and the name and type of each field. Method bodies are skipped whole and nothing is executed or interpreted.

Comments, modifiers, initialisers, generics, qualified names, arrays written either way round, records and several names in one declaration are all understood. Source that cannot be read yet is reported above the editor without throwing the classes away, and the class list is still there as a tab for changing one field.

`long`, `short` and `byte` count as primitives now. They were missing, so a field declared with one of them became a reference instead of a number.

`parseJavaClasses` and `toJavaSource` are exported.
9 changes: 9 additions & 0 deletions .changeset/quiet-moons-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@openpatch/java-memory-playground-web-component": patch
"@openpatch/java-memory-playground": patch
"web": patch
---

Fix diagrams shared before method calls existed opening as the default diagram.

Links written by early versions have no `methodCalls` section at all. Reading one threw while restoring the state from the URL, and the failure was swallowed, so the playground silently showed its default diagram instead of the one the link pointed at. Persisted state now goes through the same normalization as the `memory` prop, and building the graph tolerates a diagram that is missing whole sections.
17 changes: 17 additions & 0 deletions .changeset/spotty-melons-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Make a diagram a sequence of steps.

A stack is defined by pushing and popping, and a single frozen picture cannot show either. A diagram now holds a list of steps, with a bar to walk through them and an **Add step** button that duplicates the step on screen so a trace is authored by changing what the next line did.

This makes a set of things showable that were not: a frame appearing on a call and gone after a return, the assignment that drops the last reference to an object, and what a parameter reassignment does and does not do to the caller.

- `step` and `onStepChange` on the component, `step` and a `stepchange` event on the custom element, so a page can drive the diagram from its prose and follow along.
- Node positions are shared across steps, so dragging a node moves it everywhere and the picture does not jump while stepping.
- Class definitions are reconciled across every step when they change, rather than only the step on screen.
- Walking through a diagram is not an undo step; changing one is.
- A one-step diagram is saved in the shape it has always had, so a link to a single picture stays readable by older versions. `hideSteps` hides the bar.
12 changes: 12 additions & 0 deletions .changeset/tall-jars-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@openpatch/java-memory-playground-web-component": minor
"@openpatch/java-memory-playground": minor
"web": minor
---

Split the playground into a reusable React package, a web component and the standalone web app.

- `@openpatch/java-memory-playground` exports a `MemoryPlayground` component that takes the diagram through a `memory` prop and reports saves through `onChange`.
- `@openpatch/java-memory-playground-web-component` registers `<java-memory-playground>` for use in any page.
- Each playground now owns its store, so several playgrounds can share a page without overwriting each other.
- URL persistence is opt-in via `setPersistence`, so an embedded playground no longer takes over the host page's URL.
12 changes: 12 additions & 0 deletions .changeset/tall-moons-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@openpatch/java-memory-playground-web-component": patch
"@openpatch/java-memory-playground": patch
"java-memory-playground-studio": patch
"web": patch
---

Fix a number field that kept counting, and a config view that could not be scrolled.

Holding the up arrow of a numeric field and letting go left the value climbing until the next click somewhere else. React Flow starts a node drag on `mousedown` and, while dragging, swallows `mouseup` in the capture phase on `window` — so the spin button began its auto-repeat but was never told to stop. Every control inside a node now carries React Flow's `nodrag` class, which keeps the drag from starting over a control in the first place. That also stops a node from being dragged around by its own buttons, and lets text be selected inside an inline String.

The configuration view is a form, and as soon as a class has a few fields it is taller than the frame. It was laid out inside a `height: 100%` box with nothing to scroll it, so anything past the bottom edge was simply unreachable wherever the playground is clipped — a VS Code webview, or an embedding page. It scrolls now.
9 changes: 9 additions & 0 deletions .changeset/tidy-donkeys-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@openpatch/java-memory-playground": patch
---

Float the node palette over the canvas instead of taking a column out of it.

The palette is a panel now, like every other control, so a small embed keeps its whole width for the diagram. Dragging a class onto the canvas works from there unchanged.

Exports are framed to the diagram's nodes rather than photographing the canvas, which crops away the empty space and keeps the palette, toolbar, step bar and collector button out of the picture.
15 changes: 15 additions & 0 deletions .changeset/tidy-pandas-pack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@openpatch/java-memory-playground": patch
---

Pack the canvas more tightly.

A diagram is read as a whole, so padding inside a node is diagram that has to go somewhere else. Rows, headers, bodies, handles, the palette and the step bar are all tighter: an object with two fields went from 102×135 to 96×85, a three-field one from 203×176 to 183×111, and a frame with three locals from 184×276 to 182×198 — a third of the height, with nothing removed.

How tight is now four custom properties on the container (`--jmp-space`, `--jmp-space-lg`, `--jmp-radius`, `--jmp-handle`) rather than a number repeated down the stylesheet, so a projector or a touch screen can loosen everything at once.

Framing the diagram reserves the space the floating panels occupy. The palette is drawn on top of the canvas, so fitting the nodes edge to edge parked the first frame of the default diagram underneath it and hid its name. A hidden panel gives its side back.

A String is as wide as what it holds. It was a fixed 80px, which was too much room for an empty one and not enough for `"Hello World!"`.

The delete mark on a frame's references is a small × rather than a full-size button. It repeats on every reference, so at full size it competed with the variable names for attention.
Loading
Loading