Saved strategies and workspace
Two files with two owners — a workspace snapshot of restorable UI state under the project's state directory, and the legacy saved-strategies JSON blob beside the store that the file-tree migration moves off, since a user's strategies are files now.
The Studio persists two things, and they are not the same kind of thing. The workspace is UI convenience state the program rewrites at will. The saved-strategy list is the user's work, and the module that owns it says in its own header that its format is being retired.
The workspace snapshot
crates/vike-studio/src/workspace.rs's StudioWorkspace holds five fields: which right-hand tool
tab is open, the editor-collapsed and tools-collapsed flags, the template dropdown's index, and the
editor's last source. StudioState::maybe_persist_workspace compares the live values against the
last-written snapshot once per frame and writes only on a change, so typing in the editor writes
once per drift rather than once per frame. Loading is best-effort: a missing or corrupt file yields
the defaults rather than a panic, and an empty restored editor_source keeps the starter script
rather than blanking the editor.
Where it lives: <project>/settings/state/studio_workspace.json. The header records the move:
it used to sit at the store root beside the strategy list, and that placement was wrong for the
same reason the pacing file's was — which tab is open describes this user's screen, not the data in
that store, so pointing the Studio at a second store silently forgot the layout. $VIKE_STATE_ROOT
names the state directory outright and wins; otherwise it is the project's settings/state, the
directory vike_model::state_path::project_state_dir reserves for program-written JSON, kept
apart from the TOMLs a human edits (settings). There is no third
location.
Adoption is a dual read, never a move: workspace_read_path falls through to the legacy
store-root file while nothing has been written to the state directory, workspace_write_path
always writes the state directory — which is what migrates an existing install on its first save —
and no old file is ever deleted.
A symlinked state path falls back silently
The shared vike_model::state_path::write_path refuses to write through a symlink rather than
truncate the link's target, and the workspace writer then falls back to the legacy store-root path.
The save lands somewhere real and never through the link — but, unlike the app-level
workspace persister, which propagates the error to the user, nothing here
says the state directory was skipped: this crate links no logger, so there is nowhere to warn.
A headless capture session that forces a tool tab never writes the workspace at all, so a capture —
or a lingering VIKE_STUDIO_TAB in a dev shell — cannot persist the forced state over the real
file.
The saved-strategy list
The Saved tool (crates/vike-studio/src/saved.rs's SavedPane) is a name box with 💾 Save (also
Ctrl+S), a list with Load and ✕ Delete per row, and Compare all. The list is
studio_strategies.json, pretty JSON, colocated with the store: StudioState's state_dir is
the caller-supplied stand-in for the store root, and vike-desktop passes its resolved local
history-store root, so a local session keeps the exact old location and a remote session keeps its
strategies at the same stable local path. Every write goes through StudioState::persist_saved,
and a write failure is swallowed — the in-memory list stays authoritative for the session.
An entry is either a Rhai script or a Native registry strategy plus its free-form parameter
rows, stored as text because the harness registry has no
parameter spec to type them against. Loading a Rhai entry replaces the editor buffer and
re-baselines the unsaved dot; loading a native entry restores the strategy source instead of
blanking the editor with its empty code, and opens the Strategy tool. StrategySource's doc marks
backward compatibility as load-bearing: files written before native entries existed have no
source key, the field defaults to Rhai, and reordering the enum so Native became the default
would silently reinterpret every legacy script as a registry name.
Compare all backtests every saved strategy over the selected slice on a worker thread — a Rhai
script and a native strategy rank side by side — and comparison_rows builds the table:
successful rows by annualised Sharpe descending with NaN Sharpes last among them, then every
failed row with its reason inline rather than dropped, which the doc names as the previous
behaviour. The Kind column says what was ranked, the ★ marks the best, and each successful row
carries a sparkline.
Why the blob is being retired
saved.rs's header states it: this blob is being retired in favour of files.
crates/vike-studio-core/src/user_strategies/mod.rs loads a user's strategies from
<project>/user_data/strategies/, one folder per strategy, and argues the blob's two defects. One
corrupt byte loses the library — load_strategies answers a parse failure with an empty list, the
right call for a window layout and the wrong one for the only copy of somebody's work. And it was
filed under the wrong owner: inside the history store, re-downloadable market data whose lifecycle
is delete-and-re-ingest. A file can also be opened in an editor, diffed, committed and sent to
somebody. User strategies is the loader;
presets is what a .toml beside an entry file means.
The migration is user_strategies/migrate.rs, covered in full by
migration. From this side: SavedStrategy::legacy_entry is the one
place the JSON schema becomes the migration's vocabulary, kept beside the schema because the lower
crate cannot name the type. A Rhai row becomes strategies/rhai/<name>/<name>.rhai, verbatim; a
native row carries no code and becomes a preset for its registry strategy,
strategies/rust/<native>/<entry-name>.toml, its text rows serialised through the same
params_from_rows the run-time path uses, so 2 stays an integer and a bare BTCUSDT becomes the
quoted string it would have been at run time. Every row it cannot migrate is named — unnameable,
nameless native, a collision between two names that slug to one stem (case-folded, because Fast
and fast are one file on Windows), already present, write failed, unserialisable preset. It
writes only where nothing exists, so a second run writes nothing, and it never reads, writes, moves
or deletes studio_strategies.json — it does not even take its path. After the migration the JSON
is a read-only fallback a rolled-back build still finds.
The pane still writes the blob
legacy_entry's doc says load_strategies stays and save_strategies is what the migration
retires — but as of this build the Saved pane's Save and Delete still persist through
save_strategies, so the JSON remains the pane's live store. Which binary runs the migration, and
when, is stated by neither module.
Results
The tabbed pane over a backtest result — equity, trades, performance, the returns distribution, and a validation tab that combines the probabilistic Sharpe of one run with the deflated Sharpe, PBO and walk-forward consistency a sweep and a walk-forward add.
The AI copilot
The Studio's chat pane sends a description to a model, compiles and backtests what comes back on held-out data, and shows a diff before anything touches the editor.