vike
Desktop appStudio

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.

On this page