Every Editor preference was per-MACHINE: autocomplete, shortcut ideas, the anchor toggle and the custom-i2v bypass in browser localStorage, and the shortcut revolver in a server-side sidecar next to the shortcut DB. A freshly rented box has neither, so all of them silently revert to their defaults and have to be set by hand again — the revolver's Random mode every single time. They now ride along in the project file (Project.editor_settings), and opening a project puts them back. The revolver is still a server setting shared by every browser and by generation; the project only restores it, and only POSTs when the value actually differs, so opening a project that already agrees never restarts a live cycle. Two cases that would otherwise leave the feature useless for an existing setup: * A project saved before this existed, opened on a configured machine, adopts the machine's current settings instead of staying blank — including reading the server's current revolver mode. Nothing changes in the moment; it just stops the project from travelling empty. * setEditorSetting mirrors into the project BEFORE its unchanged-value return. Re-picking a value you already have is a no-op, so without this the settings you never touch again — exactly the ones you'd lose — would never be recorded. An ABSENT key still means "leave this browser alone": loading an old project must never overwrite a configured setting with a default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
2.9 KiB
Python
67 lines
2.9 KiB
Python
"""Editor preferences riding along in the project file.
|
|
|
|
Autocomplete, shortcut ideas, the anchor toggle, the custom-i2v bypass and the shortcut
|
|
revolver are all per-MACHINE: the first four live in the browser's localStorage and the
|
|
revolver in a server-side sidecar next to the shortcut DB. Renting a fresh GPU box gives
|
|
you neither, so every one of them silently reverts to its default and has to be set again.
|
|
`Project.editor_settings` is what carries them across, so the invariants worth pinning are:
|
|
|
|
1. It survives a save/load round trip of the project file at all (the Project dataclass is
|
|
a whitelist — an unknown key is dropped, not stored).
|
|
2. It is optional. A project written before this existed loads with an empty dict, never a
|
|
missing attribute and never a set of defaults that would overwrite a configured browser.
|
|
3. Nothing in the generation path reads it — it is editor preference, not a render setting.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from movie_editor.backend.timeline import Project # noqa: E402
|
|
|
|
|
|
SETTINGS = {
|
|
"autocomplete": False,
|
|
"suggestions": True,
|
|
"anchorEnabled": False,
|
|
"anchorGuideHasI2v": True,
|
|
"anchorGuideBypass": {"node": "slot_1", "input": "strength", "value": 0.0},
|
|
"revolver": {"enabled": True, "random": True},
|
|
}
|
|
|
|
|
|
def test_editor_settings_survive_a_project_round_trip():
|
|
p = Project.from_dict({"name": "trip", "editor_settings": SETTINGS})
|
|
assert p.editor_settings == SETTINGS
|
|
# to_dict -> from_dict is exactly what save/load does on disk.
|
|
assert Project.from_dict(p.to_dict()).editor_settings == SETTINGS
|
|
|
|
|
|
def test_a_project_saved_before_this_existed_carries_nothing():
|
|
"""An ABSENT key must mean 'leave this browser alone'. If old projects loaded a full
|
|
set of defaults instead, opening one would quietly switch off a revolver you had on."""
|
|
p = Project.from_dict({"name": "old"})
|
|
assert p.editor_settings == {}
|
|
assert Project.from_dict(p.to_dict()).editor_settings == {}
|
|
|
|
|
|
@pytest.mark.parametrize("bad", [None, "", 0, []])
|
|
def test_a_malformed_value_degrades_to_empty_rather_than_raising(bad):
|
|
assert Project.from_dict({"name": "x", "editor_settings": bad}).editor_settings == {}
|
|
|
|
|
|
def test_two_projects_keep_their_own_preferences():
|
|
a = Project.from_dict({"name": "a", "editor_settings": {"anchorEnabled": True}})
|
|
b = Project.from_dict({"name": "b", "editor_settings": {"anchorEnabled": False}})
|
|
assert a.editor_settings["anchorEnabled"] is True
|
|
assert b.editor_settings["anchorEnabled"] is False
|
|
|
|
|
|
def test_generation_never_reads_editor_settings():
|
|
"""It is a preference, not a render setting: the build path must not branch on it, or
|
|
two machines with different editor prefs would generate different video."""
|
|
src = (Path(__file__).resolve().parents[1] / "movie_editor" / "backend" / "builder.py").read_text()
|
|
assert "editor_settings" not in src
|