Reproduce the outdated vcell.org tutorials as replayable scripts - #2067
Open
jcschaff wants to merge 19 commits into
Open
Reproduce the outdated vcell.org tutorials as replayable scripts#2067jcschaff wants to merge 19 commits into
jcschaff wants to merge 19 commits into
Conversation
…no-op Driving a real tutorial through the bridge turned up seven ways a step could report success while changing nothing. Each is fixed at the point where the tool was already trying to do the right thing. Values in table cells - The recorder computed a table row but never stored WHICH COLUMN was clicked, and replay could not send one. These editor tables carry one model value per column - initial condition, diffusion constant, size - so every recording replayed its edit into the wrong cell. Captured now as `column` + `columnName`, resolved by header on replay via a new /findColumn. - Added /setCell, which commits through the table's own setValueAt - what the cell editor calls on Enter. The editor is created on edit and destroyed on commit and has no name, so it cannot be addressed any other way. - setText did not commit. Much of the older GUI reads a field from a focusLost handler rather than on Enter (TimeBoundsPanel, the mesh and output panels); setText moves no focus, so mesh size, ending time and maximum time step were displayed and then discarded. It now delivers the focusLost the user's click-away would have produced, calling the listeners directly because AWT drops a synthetic FOCUS_LOST aimed at a component that never held focus. Saying what a thing IS rather than where it sat - `text=` and `type=` join `name=` as selector forms. Pop-up menu items are the case that matters: VCell builds them on the fly with no setName, so "In Compartment Cyt" was addressable only as 1/0/1/0/0/2. /find already matched on text while the acting endpoints did not, which made `text=` look supported and then quietly resolve to nothing. - An unqualified selector now prefers the ACTIVE window, so `text=OK` means the button in the dialog in front of you, not one behind it. - Row text was the raw model object. findRow, setCell and the recorder stored Feature@4d973a55(name=EC) - an identity hash that differs every launch - where they meant "what the row displays". Replay then fell back to positional indices. All three now go through the renderer, as the tree path already did, and the /tree dump does too so what you read matches what you can match. - selectTab accepts a title. The application tab strip is built from the application: spatial shows four tabs, non-spatial five, so an index names a different tab in each. - selectCombo picks a drop-down item by label; the dump lists the items, so you can see what is selectable instead of guessing an index. - The recorder emits `text=` for buttons and menu items that have no name, and records "text" as its own durability tier. Restricted to AbstractButton: a JLabel's text is often a live value. bridge.sh findrow built its flag with an unquoted command substitution, so any query containing a space was word-split before curl saw it - findrow "Analytic Equations (2D)" searched for "Analytic" and silently matched the 1D row. Verified against the running client: smoke.sh, detach-window.sh (18/18) and detach-window-recorded.sh (15/15) all still pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
A UI script can only address a component durably if it has a name; otherwise it falls back to a node path, which breaks the next time the panel is rearranged. These are the components a script driving the model editor has to touch. - BioModelEditorModelPanel: the five right-side tables were unnamed, so the structures table was reachable only as 0/0/1/0/1/0/0/2/2/0/2/0/0/0. - GeometrySubVolumePanel, StructureMappingPanel and SimulationListPanel each called their table "ScrollPaneTable"/"ScrollPaneTable1" - names eight other panels also use, left over from the VisualAge generator. A replay looking for the simulation list could resolve the geometry subdomain table instead. Now SubVolumesTable, StructureMappingTable and SimulationsTable. Nothing looks these up by name, so the rename is safe. - AddShapeJPanel: the shape panels were named but their ten text fields were not, leaving the circle radius addressable only as "the third text field currently reading 1.0". Named after construction, since several are assigned partway through the initializer. No behaviour change; setName is only an identifier. Files under vcell-client are CRLF and the additions preserve that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…ript for FRAP The tutorial PDFs at vcell.org/webstart/VCell_Tutorials/ are screenshot decks shot against VCell 6.1-7.2. Six were refreshed in July 2025 (the 7.7/ subtree); the ten here still describe a client that has moved on. Each gets a storyline - what the document teaches, in prose, extracted from the PDF - so the content survives independently of the deck. Simple FRAP also gets a script that rebuilds it against a current client, end to end, in ~100 seconds with zero errors: physiology, application, analytic 2D geometry, structure mapping, specifications and simulation settings. It stops before Save and Run, which need an account and dispatch a real job to shared compute. The tutorials all build their physiology by DRAWING on the Reaction Diagram, and the recorder deliberately never stores coordinates. The way round it is that the diagram is not the only route - the Quick Start guide says species "can be specified and edited in both Structure and Reaction Diagram views" - so the script drives the table views, where "type 20 in the Diffusion Constant column" is a sentence it can say literally. What has no table equivalent is called out per tutorial: image segmentation in MultiAppTransport is genuinely out of reach. Two findings worth acting on separately: - Simple FRAP never tells you to map the membrane. Mapping only EC and Cyt leaves PM reading "Unmapped" while the model still reports 0 errors - but VCell then picks SundialsPDE rather than Fully-Implicit. Followed literally, the tutorial can produce a different simulation than the one it teaches. The script maps it and asserts the mapping resolved rather than letting a silent no-op flow downstream. - SpatialRuleBasedGuide is the only rule-based topic the 7.7 refresh did not cover, and Tutorial06_PathwayCommons depends on a third-party service whose current state was not verified. Both artifacts are kept because they are not equivalent: the shell scenario resolves rows and columns at run time and asserts, so it is what to verify with; the JSON recording is a faithful linear capture, which is what to use for screenshots (replay.py --shots) or a filmed walkthrough (--driver robot). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…tealing focus VCELL_UI_BACKGROUND=true adds -Dapple.awt.UIElement=true, which registers the client as a macOS accessory app: no Dock icon, no menu bar, and it never becomes the active application. That matters because activating a window pulls the whole desktop to whichever Space it is on, and an ordinary app activates itself both on launch and every time a modal dialog opens - so a scripted run repeatedly yanked the user off whatever they were doing. Verified by sampling the frontmost process every 5s through a full tutorial run: it stayed on the user's own applications throughout, dialogs included, and every step still worked - the bridge drives the model rather than real input, so it needs neither focus nor a Dock icon. Documented alongside it: this does NOT hide the window, and it makes /iconify report a state the window server has not honoured. Frame.setExtendedState accepts ICONIFIED and the bridge faithfully repeats it, but with no Dock icon there is nowhere to minimise to and the window stays visible - so "iconified": true from the bridge is not evidence the window is gone. Off by default; removing the menu bar makes it wrong for driving the client by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
rrow, rclick, drow, dtrow, rtrow and the non-button branch of click all issued a
java.awt.Robot click at absolute screen coordinates. That is the wrong default
for a tool whose whole point is to drive the model:
- it moves the user's real cursor and clicks for them, so a scripted run fights
whoever is at the keyboard;
- it lands wherever the window happens to be, so moving the client out of the
way silently breaks it;
- it needs the application to be ACTIVE. Run the client as a macOS accessory app
and a real right-click never raises the context menu, while the endpoint still
reports {"rightClicked": true} - which is how this was found: a tutorial step
reported success and no menu had opened.
They now dispatch the events to the component in its own coordinates. Swing
decides to show a context menu from MouseEvent.isPopupTrigger(), so a
press/release pair carrying that flag reaches the same listeners the real gesture
would; the flag is set on both because which one carries it is platform-specific.
glide and robotClick keep the Robot, which is their entire purpose.
Also: doClick() on a menu item never closed the menu. A real click dismisses the
pop-up on its way to firing the action; doClick only fires the action, so the
stale "New Application" submenu stayed open and shadowed the next text= lookup -
and the failure surfaced several steps later, nowhere near its cause. Clicking a
JMenuItem now clears the menu selection path first (first, not after, so it does
not race a dialog the action opens).
Names the subdomain shape combo, the last addressable-only-by-path control in the
FRAP tutorial's path.
Verified: smoke.sh, detach-window.sh (18/18) and detach-window-recorded.sh
(15/15) all pass, and the FRAP tutorial now runs end to end with the client as a
non-activating accessory app parked off to one side.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…the FRAP script
_common.sh factors out the helpers the tutorial scenarios share, with one rule
behind all of them: never let a step that did not happen pass for one that did.
- must() runs a bridge verb and stops if the reply says it did not take. Every
acting endpoint answers {"clicked": true} / {"set": false} / ..., and piping
that to /dev/null is how a mistake becomes invisible.
- row() and col() resolve by displayed text and column header, and stop on a
miss rather than returning -1 into the next call.
- navselect() exists because `trow` is selectTableRow: calling it on a JTree
returns {"selected": false} and changes nothing. The tree verb is `row`.
That last one was a real bug in this script. Two steps selected nothing, and the
tutorial only worked because the Geometry tab happened to already be selected
after the application was created. must() caught it on the first run.
The recording is regenerated: 59 steps, 49 resolved by name, 8 by label, and 2 by
path - the tree's transient cell editor and a bare JSortTable inside a
JOptionPane, both of which genuinely have nothing else to be addressed by.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…amed column
Two more silent failures, both found building the Moving Boundary tutorial.
click() drove a button with doClick(), which invokes only the ACTION listeners.
Not every button keeps its behaviour there: the Kinematics "New" button builds
and shows its pop-up from a MouseAdapter.mousePressed, so doClick fired the
(empty) action, reported success, and opened nothing. Buttons now get a real
press/release, which drives the button's own UI listener - firing the action
exactly once - and also reaches the mouse listeners, so both wirings work. Menu
items keep doClick, because a menu item's behaviour always lives in its action.
findRow only ever matched column 0. That is the row's identity in most of these
tables but not all: a spatial process's parameter table leads with a prose
description ("surface velocity (x coord)") and carries the name the tutorial
actually says - velocityX - in the next column. Searching column 0 found nothing
and returned -1, which the caller then passed to setCell as a row index. It now
takes an optional column to search, named rather than indexed:
bridge.sh findrow name=SortTable velocityX --in Parameter
Names the four controls on the Kinematics tab. It has two tables and two pairs
of "New"/"Delete Selected" buttons that are identical by label and position, so
without names there was no way to say which one a step meant: SpatialObjectsTable,
SpatialProcessesTable, and their New/Delete buttons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…kups wait Second of the outdated tutorials to run end to end. It is Simple FRAP in a cell that MOVES: same physiology, but the membrane carries a velocity and the domain deforms as the simulation runs. Both spatial processes end up with velocityX = 4 and velocityY = 5*sin(10*t), the volume process VCell creates for the wrong object is deleted, and the model reports 0 errors. Three things the Kinematics tab teaches the hard way, all now in the script and its storyline: - Volume kinematics lands on the wrong object first. VCell creates a process for the next volume in the spatial-object table each time, so the first attempt targets EC and a second is needed to reach Cyt. The tutorial's "delete the one you did not want" is not a nicety. - The Interior Velocity checkbox is on the OBJECT, not the process. A volume process reports an error until its spatial object is allowed to have one. - A panel's columns arrive before its rows. Selecting a process yields the four parameter headers immediately and the velocity rows a moment later, so `col` succeeded while `row` still saw nothing and returned -1. row() and col() now RETRY for 10s rather than trusting a fixed sleep - which is the general fix for a whole class of flake these scripts would otherwise have. SpatialProcessPropertyPanel called itself "SpatialObjectPropertyPanel" - a copy-paste slip that gave two different panels the same name - and both panels named their table "SortTable", so a lookup landed on whichever was found first. Now SpatialProcessParametersTable and SpatialObjectQuantitiesTable. Verified: moving-boundary.sh exits 0 with both processes carrying the tutorial's velocities, and simple-frap.sh still exits 0 with End Time 3.0 and 0 errors, so the retry change is not a regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
"Needs reaction creation" was too vague to act on. The specific obstacle, read off BioModelEditorReactionTableModel.setValueAt: An equation typed into the "(add new here, e.g. a+b->c)" row is only accepted when the model has EXACTLY ONE STRUCTURE - the same restriction the species table has, and the reason simple-frap.sh creates its species through the New Species button's "choose compartment" menu instead. FRAPBinding has three structures and PHGFP five, so that route is closed for both. Editing the Equation column of an EXISTING reaction carries no such restriction, and does the useful part: it parses the equation and creates any species it names that do not exist yet. So the sequence is New Reaction -> choose compartment -> set the equation, mirroring the species route that already works. Read from the table model, not yet verified against the running client, and the storylines say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
Correcting the previous commit, which said editing an existing reaction's Equation column "creates any species it names" and left it there. True, but misleading in a way that matters: it creates them in the REACTION's compartment, and there is no way to say otherwise from the equation text. ModelProcessEquation.parseReaction resolves each name with model.getSpeciesContext(var) across the whole model and reuses it wherever it already lives; only an unrecognised name becomes new SpeciesContext(species, rxnStructure). So auto-creation is correct only when every participant belongs in the compartment the reaction sits in. FRAPBinding happens to satisfy that - all six species and all four reactions are in Nuc - but PHGFP does not: PIP2_PM and PIP2_PHGFP_PM are on the membrane while PH_GFP_Cyt and IP3_Cyt are in the cytosol, so "PIP2_PM + PH_GFP_Cyt -> PIP2_PHGFP_PM" spans two compartments and typing it against a model missing those species would put all three in one, silently and with no error. MultiAppTransport has the same shape around its NM flux. The recipe is therefore: create every species through New Species -> choose compartment first, exactly as simple-frap.sh already does, and let the equation only wire up species that exist. Also corrects where the placeholder lives: in the Reactions table "(add new here, e.g. a+b->c)" is in the EQUATION column, not the Name column as it is for structures and species - getValueAt returns a ModelProcessEquation for COLUMN_EQUATION on that row and null for every other column. Thanks to Jim for catching the compartment point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…rface
Completes the reaction-creation recipe. Choosing the species' compartments is
only half of it; the reaction itself has one too, and New Reaction asks which.
The rule, from Jim: a localized reaction spanning more than one compartment must
occur where those compartments meet - the N-1 dimensional interface between them,
which is the membrane. A reaction whose participants all share one compartment
simply goes there. Nothing in the UI enforces this, so a script has to choose
deliberately rather than take whatever validates.
Recorded per reaction where the models actually differ:
PHGFP PIP2_PH PIP2_PM + PH_GFP_Cyt -> PIP2_PHGFP_PM PM (membrane + volume)
IP3PH IP3_Cyt + PH_GFP_Cyt -> IP3_PHGFP_Cyt Cyt
r2 -> IP3_Cyt, catalysed by Stim Cyt
MultiApp the RanC flux across the nuclear membrane NM
the cytoplasmic reaction Cyt
FRAPBinding needs no such distinction - every species and every reaction is in
Nuc - which is precisely why it is a poor model to generalise the recipe from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…on network Third tutorial to run end to end, and the first whose physiology is a REACTION NETWORK rather than a single diffusing species. Builds three structures, six species, four reactions and the compartmental application, finishing with 0 errors: sizes Cyt 523.33 / Nuc 26.1665 / NM 130.8325, initial conditions r 5.0 / rf 5.0 / BS 20.0, and a simulation to 30 s. Three rules make a reaction network expressible in the table views, all of them load-bearing and none of them visible from the PDF, which draws on the canvas: - Species first. An equation cannot place a species, so every species is created through New Species -> choose compartment before any equation is typed. - Each reaction in the compartment where its participants meet. Everything here is in Nuc, so every reaction is too. - A catalyst is never written in the equation. The grammar is only "reactants -> products"; catalysts neither parse nor render, and setting the Equation column calls setReactionParticipants with reactants and products alone. A catalyst is implied by the KINETIC LAW - a rate expression naming a species that is neither reactant nor product makes it one. Confirmed against the running client: after setting the bleaching rate to (Vmax*rf*Laser*((t>1.0)&&(t<1.5))), Laser appears in the reaction's parameter table as a Variable and the model reports no errors. Thanks to Jim for the explanation; I had been looking for a way to declare it explicitly, and there isn't one. The New Reaction dialog turned out to be the right seam: it asks for all three of a reaction's defining properties at once - where it occurs, its name, its equation - so there is no half-built intermediate state. Its three controls are now named. ReactionPropertiesPanel called itself "KineticsTypeTemplatePanel" - the third copy-paste panel-name collision in this branch, after SpatialProcessPropertyPanel - so the name resolved to a class that was not the panel on screen and the first run edited nothing. Its kinetics table and type combo shared the generic "JComboBox1"/unnamed forms with two other panels; both now have their own names, as does KineticsTypeTemplatePanel's equivalent pair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
…uick Run The Edit Simulation dialog is unusual: it CLONES the simulation, edits the clone, and replaces the original in the document only on OK. So making setText commit - the earlier focusLost fix - only got the value into the clone. Closing the dialog is what writes the clone back, and leaving it open discards every edit silently, with the simulation table still showing the old numbers. The scripts closed it with a bare `click text=OK`, which resolves against every showing window and can land on a different OK. It usually worked and sometimes did not: one run quick-ran a 1.0 s simulation while the open dialog read 30.0. dialog_button now resolves the button INSIDE the named dialog and then waits for that dialog to actually disappear, so a failure to close is an error rather than a silently discarded edit. Also names the Quick Run button. It runs a simulation on this machine without saving the document to the database, which is what lets a scripted tutorial produce real results: the local install carries every solver these tutorials need (SundialsSolverStandalone for ODE, FiniteVolume for PDE, MovingBoundary, plus the stochastic ones), and the results window exposes its species list and data table to the bridge. That is the missing piece for the tutorials whose later steps read values off an earlier run. Thanks to Jim for the clone-and-replace explanation; I had the symptom but not the mechanism. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
Jim's call: prefer Quick Run, so nothing has to be saved to the database. That turns out to unlock more than the compartmental runs - the local install carries every solver these tutorials need (SundialsSolverStandalone, FiniteVolume, MovingBoundary, VCellStoch, NFsim, smoldyn, langevin), and canQuickRun refuses only parallel solvers, server-only features and missing executables. simple-frap.sh is now a COMPLETE reproduction: it builds the model and runs it, finishing with a "Results for Simulation FRAP" window from the bundled FiniteVolume solver, with no account and nothing written to the server. New bridge verb /readCell, because the tree dump truncates a table to 25 rows. That is right for reading a UI and useless for reading a RESULT: a steady state is the last row of a series hundreds long, and that is precisely the row a tutorial copies into its next application. readCell takes a row index (negative counts back, so -1 is the last row) and a column by header, and returns the rendered text. The row count was already reported un-truncated, so the caller can always find the end. Two helpers in _common.sh: quick_run waits for the results window rather than sleeping, and result_cell reads one value out of it. Also fixes a flake the assertions caught: `click text=Rename` failed on one run because a context menu is transient and built from a dispatched mouse event, so a fixed sleep between opening it and clicking an item is a race. tree_pick and menu_pick retry the open-and-click as a UNIT - reopening a menu after a miss is harmless, since a second right-click just replaces it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
… run
Completes the loop Jim asked for: run locally, no database save, and now read the
results back so a tutorial's later steps can use them.
FRAP with binding takes the steady state of its compartmental run as the initial
conditions of its spatial one - the step I had called a wall, on the grounds that
those numbers only exist after a run. They do, but the run can be local. The
script now quick-runs the ODE application and reads the end of the time course:
BS 12.807787 rB 3.5961066 rf 1.4038934 rfB 3.5961066
Checked rather than assumed: rB equals rfB because RAN and RAN-FITC start at 5.0
each and compete symmetrically for the same sites; rf + rfB is exactly 5.0 and
BS + rB + rfB exactly 20.0. Both conservation laws hold, so this is the solver's
arithmetic and not a plausible-looking artifact.
Two new verbs were needed:
- /selectList picks list items by their displayed label, comma-separated for a
multi-select. Which species are selected in the results window decides which
COLUMNS the data table has, so reading four concentrations means selecting four
names first - and the tutorials say "Ctrl-click the other species", never
"select rows 0, 3, 4 and 5".
- /readCell reads one cell without the tree dump's 25-row cap, with a negative row
counting back from the end. The cap is right for reading a UI and useless for
reading a result.
Naming the results table was not enough on its own. TWO PlotDataTables exist - the
document window keeps its own, empty - and NEITHER reports isShowing, because the
results window opens on the plot with the spreadsheet as a hidden card that still
holds the data. So the usual "prefer what is showing" tie-break had nothing to
work with and a bare name landed on the wrong one. result_cell resolves the table
by path within the results window instead.
Also: macOS ships bash 3.2, which has no associative arrays.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
The whole tutorial now reproduces: physiology, the compartmental application, a local run, the steady state read off its results, and the spatial application built from those numbers. Two applications, 0 errors, and a spatial simulation at 50 s / every 0.5 s on Fully-Implicit - the PDF's parameters, on a solver that can run them. Setting each species from its own steady state, rather than the PDF's copy-four- cells-and-Paste-All, says the same thing without depending on the row order of either table. r and rf both come out at 1.4038934 and rB and rfB at 3.5961066, which is the symmetry the model implies. Copying an application is not quite a copy, and none of this is in the PDF: - It brings the SIMULATIONS with it, still carrying the compartmental ODE solver. "Combined Stiff Solver (IDA/CVODE) does not support ... Spatial". Delete them. - Adding a geometry leaves the generated math stale, and VCell then refuses to open the Edit Simulation dialog at all: "Application geometry does not match Simulation geometry - Update Math before editing". RefreshMathButton on the Generated Math tab clears it. - That same refresh settles the solver. A simulation created against stale math inherits the copied application's ODE solver; created after a refresh it gets Fully-Implicit. One click, two symptoms that look unrelated. Each of these surfaced as a failure several steps from its cause - the Mesh tab "not found", because an error dialog had blocked the Edit dialog from opening at all. dialog_tab scopes tab selection to the named dialog, since JTabbedPane1 is also the name of the pane in a results window, which these tutorials now open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
The value arrives over HTTP as text, but a checkbox column is not a text column: its model casts what it is handed straight to Boolean, so passing a String threw on the EDT and the caller saw nothing but a cell that had not changed - the exact silent failure these scripts exist to catch. Convert for columns whose declared class is Boolean. The PH-GFP tutorial needs it: Stim has to be Clamped before its initial condition may depend on t. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
Both panels were addressable only by position: the events table and the output functions table were each called "ScrollPaneTable", a name eight other panels also use, and the wizard's domain combo and Previous/Finish buttons had no name at all. The single-time field needed one too - several fields in that dialog read "1.0" by default, so their text does not identify them. No behaviour change; component names are used for lookup, not display. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
The longest of the decks (101 pp) and the one where the compartment rules earn their keep: species live on a membrane as well as in volumes, and PIP2_PM + PH_GFP_Cyt -> PIP2_PHGFP_PM spans both, so it belongs on the interface between them rather than in either. Both applications, end to end, 0 errors: - physiology - five nested structures, six species placed by compartment, three reactions each where its participants meet - a compartmental application with the sizes, the resting concentrations and two timed events, run locally with IDA - a 3D spatial application - a sphere of cytosol with an off-centre nucleus, built from analytic equations - carrying the resting state across, with Stim clamped to a time window and a Fluorescence output function Two things the tooling did not have: - result_row_at_time. The value the spatial half needs is NOT the end of the compartmental run: an event fires at t = 5 s, so the last row is a stimulated state. The tutorial reads t = 5 instead, and a variable-time-step integrator picks its own output times, so there is no row number to hard-code. Binary-searching the monotonic time column finds it in about nine reads and cannot land on the wrong instant. - result_table_path, factored out of result_cell so both share one lookup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The tutorial PDFs at
vcell.org/webstart/VCell_Tutorials/are screenshot decks shot against VCell 6.1–7.2. Six were refreshed in July 2025 (the7.7/subtree); ten still describe a client that has moved on.This extracts a storyline from each of those ten — what the document teaches, in prose, with its exact parameter values — and turns four of them into scripts that rebuild the model against a current client, run it, and read the results back.
What runs
SimpleFRAP_7.2.pdfMovingBoundaries.pdfFRAPBinding_7.2.pdfPHGFP_7.2.pdfMultiAppTransportPathwayCommonsNo script touches
File > Saveor the green Run button: saving needs an account and Run dispatches a real job to shared compute. They use Native Quick Run instead, which executes with the bundled local solvers and saves nothing — so a scripted tutorial produces real results without an account and without putting anything on the server.Reading results back is what makes the two multi-stage tutorials reachable. Each takes the steady state of its compartmental run as the initial conditions of its spatial one, the way the PDF's reader does — off the results table:
rBequalsrfBbecause RAN and RAN-FITC start at 5.0 each and compete symmetrically for the same sites;rf + rfBis exactly 5.0 andBS + rB + rfBexactly 20.0. Both conservation laws hold.result_row_at_timebinary-searches the monotonic time column, about nine reads, and cannot land on the wrong instant.PH-GFP is also where the compartment rules earn their keep.
PIP2_PM + PH_GFP_Cyt -> PIP2_PHGFP_PMjoins a membrane species to a volume species, so the reaction belongs onPM— the interface where they meet — not in either compartment. Nothing in the UI enforces that, and typing the equation into a model that does not already have those species would have created all three in the reaction's own structure, silently and with no error.The canvas problem
Every tutorial builds its physiology by drawing on the Reaction Diagram — "hover on the dotted black lines so they turn green". Those are pixel gestures on a custom-painted canvas, and the recorder deliberately never stores coordinates.
The way round it is that the diagram is not the only route. The Quick Start guide says so outright: species "can be specified and edited in both Structure and Reaction Diagram views". So the scripts drive the table views, where "type 20 in the Diffusion Constant column" is a sentence a script can say literally.
Most of this diff is bridge fixes, not scripts
simple-frap.shdid not work against the bridge as it stood. Each of these reported success while changing nothing:setTextnever committed. Much of the older GUI reads a field from afocusLosthandler rather than on Enter (TimeBoundsPanel, the mesh and output panels).setTextmoves no focus, so mesh size, ending time and maximum time step were displayed and then discarded — the dialog reopened showing the old numbers. Fixed by calling the focus listeners directly; a syntheticFOCUS_LOSTviadispatchEventis dropped by AWT for a component that never held focus.doClick()never closed a menu. A real click dismisses the pop-up on its way to firing the action. The stale "New Application" submenu then shadowed the nexttext=lookup, and the failure surfaced several steps later, nowhere near its cause.clickuseddoClick(), which invokes only the action listeners — the Kinematics "New" button shows its pop-up fromMouseAdapter.mousePressed, so nothing opened. Buttons now get a real press/release.Feature@4d973a55(name=EC), an identity hash that differs every launch — wherefindRow,setCell, the recorder and the/treedump all meant "what the row displays". Replay silently fell back to positional indices.findRowonly ever matched column 0. A spatial process's parameter table leads with a prose description and carriesvelocityXin the next column. Nowfindrow … --in Parameter.text=selectors resolved in/findbut nowhere else — pop-up items are exactly the components that need them, since VCell builds them without names. Addedtext=,type=, and active-window preference.trowisselectTableRow— on a JTree it returns{"selected": false}and does nothing. Two steps in the first version of the FRAP script were silent no-ops that only worked because the Geometry tab happened to already be selected.bridge.sh findrowword-split its query, sofindrow "Analytic Equations (2D)"searched forAnalyticand quietly selected the 1D row.colsucceeded whilerowreturned -1. Lookups now retry rather than trusting a fixedsleep.focusLostwrites into the clone and closing the dialog is what writes the clone back. Leaving it open discards the lot, silently./treetruncates a table to 25 rows, which is right for reading a UI and useless for reading a result: a steady state is the last row of a series hundreds long. HencereadCell— a row index (negative counts back) and a column by header.PlotDataTables exist — the document window has its own, empty one. Resolved by path within the results window.RefreshMathButtonsettles both.setCellcould not tick a checkbox. The value arrives as text but a checkbox column's model casts it straight toBoolean, so a String threw on the EDT and the caller saw only a cell that had not changed. PH-GFP needs it:Stimhas to be Clamped before its initial condition may depend ont.No scripted verb drives the physical mouse any more.
rrow,rclick,drow,dtrowandclick's non-button branch all issuedjava.awt.Robotclicks at absolute screen coordinates — moving the user's real cursor, breaking if the window moved, and silently failing when the app could not be activated. They now dispatch AWT events.glideandrobotClickkeep the Robot; that is their purpose._common.sh'smusthelper stops a run when the bridge reports a step did not take. Most of the list above was found by it.Naming debt, fixed at source
ScrollPaneTableandSortTablewere each used by eight or more panels, so a lookup could land on the wrong table.SpatialProcessPropertyPanelcalled itself"SpatialObjectPropertyPanel"— a copy-paste slip giving two panels the same name. Named: the five model editor tables,SubVolumesTable,StructureMappingTable,SimulationsTable,SpatialObjectsTable,SpatialProcessesTableand their New/Delete buttons,SpatialProcessParametersTable,SpatialObjectQuantitiesTable,subdomainShapeComboBox, ten shape fields inAddShapeJPanel, the kinetics and plot-data tables, and — for the two later tutorials —EventsTable,EventActionsTable,EventSingleTimeTextField,OutputFunctionsTable,FunctionDomainComboBox,PreviousButtonandFinishButton. Nothing looked any of these up by name.A finding for whoever owns the tutorials
Simple FRAP never tells you to map the membrane. Mapping only
ECandCytleavesPMreading Unmapped while the model still reports 0 errors — but VCell then picks SundialsPDE rather than Fully-Implicit. Followed literally, the tutorial can produce a different simulation than the one it is teaching. The script maps it and asserts the mapping resolved.PH-GFP's spatial half is built with every diffusion constant left at its default, and the PDF never mentions them. Not something the script should fix by inventing numbers, but worth an author's attention: the whole point of that application is that the fluorescent signal spreads.
Separately:
SpatialRuleBasedGuideis the only rule-based topic the 7.7 refresh did not cover — the gap in the current documentation set.Running it without the window in your face (macOS)
VCELL_UI_BACKGROUND=true tools/debug-bridge/launch-client.shadds-Dapple.awt.UIElement=true: no Dock icon, never becomes the active application, so it cannot pull you to its Space when a modal dialog opens. Verified by sampling the frontmost process through a full run. It does not hide the window, and/iconifyreports a state the window server has not honoured in that mode — worth knowing before trusting the flag.Verification
velocityX = 4,velocityY = 5*sin(10*t).PM→Cyt_EC_membrane,NM→Cyt_Nuc_membrane), the three analytic subdomains,Stimclamped with((t > 5.0) && (t < 6.0)), the spatial simulation on Fully-Implicit at 20.0 s every 0.2 s, andFluorescence = (IP3_PHGFP_Cyt + PH_GFP_Cyt)defined inCyt.smoke.sh,detach-window.sh(18/18),detach-window-recorded.sh(15/15).mvn test -pl vcell-client -am -Dgroups=Fast: 606 run, 0 failures, 1 error —VCellDataTest, failing onModuleNotFoundError: No module named 'vtkmodules', the missing-Poetry-env failure documented in CLAUDE.md, in a module this branch does not touch.Note for reviewers
The bridge changes are behind
-Dvcell.debugBridge=trueand ship inert. ThesetNameadditions and the two renamed panels are the only changes to production UI code;setNameis an identifier only, and files undervcell-clientare CRLF with the additions preserving that.🤖 Generated with Claude Code
https://claude.ai/code/session_019HAnpFxkzf9LmxBayDSANf