fix(ui): scope spatial tools to scene

This commit is contained in:
DCCONSTRUCTIONS
2026-07-18 11:31:44 +03:00
parent 574a494759
commit 0ac6424c46
9 changed files with 216 additions and 142 deletions
+30 -13
View File
@@ -175,10 +175,7 @@ class SessionStore:
).fetchone()
if cursor_row is None:
raise SessionNotFoundError("observation session cursor was not found")
where = (
"WHERE (COALESCE(started_at_utc, ''), session_id) < "
"(COALESCE(?, ''), ?)"
)
where = "WHERE (COALESCE(started_at_utc, ''), session_id) < (COALESCE(?, ''), ?)"
parameters.extend((cursor_row["started_at_utc"], cursor_row["session_id"]))
parameters.append(limit + 1)
rows = connection.execute(
@@ -309,8 +306,7 @@ class SessionStore:
_validate_identifier(session_id, "session id")
with self._connect() as connection:
session = connection.execute(
"SELECT allowed_root, session_root FROM observation_sessions "
"WHERE session_id = ?",
"SELECT allowed_root, session_root FROM observation_sessions WHERE session_id = ?",
(session_id,),
).fetchone()
if session is None:
@@ -372,8 +368,8 @@ class SessionStore:
layout: dict[str, Any],
) -> WorkspaceLayout:
_validate_identifier(workspace_id, "workspace id")
if schema_version != 1:
raise ValueError("only workspace layout schema version 1 is supported")
if schema_version not in {1, 2}:
raise ValueError("workspace layout schema version is unsupported")
if expected_revision < 0:
raise ValueError("expected revision must be non-negative")
normalized_name = name.strip()
@@ -423,9 +419,9 @@ class SessionStore:
def _initialize(self) -> None:
with self._connect() as connection:
connection.executescript(SCHEMA_SQL)
self._migrate_transient_tool_windows(connection)
session_columns = {
row["name"]
for row in connection.execute("PRAGMA table_info(observation_sessions)")
row["name"] for row in connection.execute("PRAGMA table_info(observation_sessions)")
}
for name, declaration in (
("plugin_id", "TEXT NOT NULL DEFAULT ''"),
@@ -441,9 +437,7 @@ class SessionStore:
)
artifact_columns = {
row["name"]
for row in connection.execute(
"PRAGMA table_info(observation_session_artifacts)"
)
for row in connection.execute("PRAGMA table_info(observation_session_artifacts)")
}
if "replay_byte_length" not in artifact_columns:
connection.execute(
@@ -454,6 +448,29 @@ class SessionStore:
with _ignore_os_error():
self.database_path.chmod(0o600)
def _migrate_transient_tool_windows(self, connection: sqlite3.Connection) -> None:
"""Remove v1 scene-tool visibility that never belonged in a layout."""
rows = connection.execute(
"SELECT workspace_id, layout_json FROM workspace_layouts "
"WHERE workspace_id = ? AND layout_schema_version = 1",
("observation.spatial",),
).fetchall()
for row in rows:
try:
layout = json.loads(row["layout_json"])
except (TypeError, json.JSONDecodeError):
continue
if not isinstance(layout, dict) or "tool_windows" not in layout:
continue
layout.pop("tool_windows", None)
connection.execute(
"UPDATE workspace_layouts "
"SET layout_schema_version = 2, layout_json = ? "
"WHERE workspace_id = ? AND layout_schema_version = 1",
(_serialize_layout(layout), row["workspace_id"]),
)
def _upsert_candidate(
self,
source: ObservationArchiveSource,
+50 -7
View File
@@ -118,7 +118,7 @@ class SceneSettingsDocument(StrictApiModel):
show_camera_frustums: bool
class ToolWindowsDocument(StrictApiModel):
class LegacyToolWindowsDocument(StrictApiModel):
sources_open: bool
display_open: bool
layers_open: bool
@@ -151,12 +151,23 @@ class ViewportSize(StrictApiModel):
height: float = Field(gt=0.0, le=100_000.0)
class LayoutPutRequest(StrictApiModel):
class LegacyLayoutV1Document(StrictApiModel):
version: Literal[1]
revision: int = Field(ge=0, le=MAX_SAFE_INTEGER)
workspace_id: Literal["observation.spatial"]
scene_settings: SceneSettingsDocument
tool_windows: ToolWindowsDocument
tool_windows: LegacyToolWindowsDocument
visible_source_ids: list[str]
active_floating_source_id: str | None
window_rects: dict[str, NormalizedWindowRect]
viewport_size: ViewportSize
class LayoutPutRequest(StrictApiModel):
version: Literal[2]
revision: int = Field(ge=0, le=MAX_SAFE_INTEGER)
workspace_id: Literal["observation.spatial"]
scene_settings: SceneSettingsDocument
visible_source_ids: list[str]
active_floating_source_id: str | None
window_rects: dict[str, NormalizedWindowRect]
@@ -791,7 +802,12 @@ def build_session_router(
def get_workspace_layout(workspace_id: str, response: Response) -> dict[str, Any]:
try:
stored = store.get_layout(workspace_id)
document = _layout_document(stored.workspace_id, stored.revision, stored.layout)
document = _layout_document(
stored.workspace_id,
stored.schema_version,
stored.revision,
stored.layout,
)
response.headers["ETag"] = f'"{stored.revision}"'
return document
except SessionNotFoundError as exc:
@@ -824,7 +840,12 @@ def build_session_router(
layout=payload,
)
response.headers["ETag"] = f'"{stored.revision}"'
return _layout_document(stored.workspace_id, stored.revision, stored.layout)
return _layout_document(
stored.workspace_id,
stored.schema_version,
stored.revision,
stored.layout,
)
except LayoutConflictError as exc:
raise HTTPException(status_code=412, detail=str(exc)) from exc
except ValueError as exc:
@@ -885,20 +906,42 @@ def _parse_if_match(value: str) -> int:
def _layout_document(
workspace_id: str,
schema_version: int,
revision: int,
layout: dict[str, Any],
) -> dict[str, Any]:
if schema_version == 1:
try:
legacy = LegacyLayoutV1Document.model_validate(
{
"version": 1,
"revision": revision,
"workspace_id": workspace_id,
**layout,
}
)
except ValueError as exc:
raise SessionIntegrityError(
"stored workspace layout violates schema version 1"
) from exc
layout = legacy.model_dump(
mode="json",
exclude={"version", "revision", "workspace_id", "tool_windows"},
)
schema_version = 2
if schema_version != 2:
raise SessionIntegrityError("stored workspace layout schema is unsupported")
try:
document = LayoutPutRequest.model_validate(
{
"version": 1,
"version": 2,
"revision": revision,
"workspace_id": workspace_id,
**layout,
}
)
except ValueError as exc:
raise SessionIntegrityError("stored workspace layout violates schema version 1") from exc
raise SessionIntegrityError("stored workspace layout violates schema version 2") from exc
return document.model_dump(mode="json")