Skip to content

fix: db/redis bleed - #102

Open
calistaannelise wants to merge 21 commits into
devfrom
calista_vidianto/bug/db_bleed
Open

fix: db/redis bleed#102
calistaannelise wants to merge 21 commits into
devfrom
calista_vidianto/bug/db_bleed

Conversation

@calistaannelise

@calistaannelise calistaannelise commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

IMPORTANT:

  • PR must be opened from your personal branch → dev
  • You must tag @Team-Deepiri/support-team
  • You must update Plaky to "Needs QA"
  • Never move a feature/bug to "Done" (Done = production release only)

Description

Fix the DB/Redis bleed where opening or creating a project showed the previous project's timeline: generated videos and clips from the last-opened project stayed on screen because the studio shell never tore down per-project state, and clips were never round-tripped to the server per sequence. Also changed "delete project" functionality so it doesn't leave half-deleted projects behind (gone from local but persists in db).


Changes

Timeline Persistence and fix bleed between projects

  • studio.py — added ClipUpsert(ClipCreate), which adds a required id: UUID. The client mints the clip id, so a save is an upsert against existing rows instead of a delete-and-recreate.
  • timeline.py — new PUT /v1/sequences/{sequence_id}/clips. The body is a list[ClipUpsert] that becomes the sequence's complete clip list. 404s if the sequence does not exist.
  • memory_store.py — new clip_replace_for_sequence(), runs under one lock and one _save(). Clips absent from the payload are dropped, clips already present are updated in place (created_at preserved), and rows for a track outside of the sequence are ignored
  • db_repos.py — new replace_clips_for_sequence(), used to update the timeline when changing between projects
  • studio.pyreplace_clips_for_sequence() wires the memory store and the PG mirror together, returning the memory store's canonical rows

Delete Project Functionality

  • db_repos.py — added delete_project(), which deletes projects according to the id, and the three FKs into assets that carry no delete rule defaults to NO ACTION (clips.asset_id, ai_job_artifacts.asset_id, ai_jobs.result_asset_id), and added update_project() that takes the row memory_store.project_update already returns
  • studio.pydelete_project() calls delete in db first then memory, so db failure leaves the project state untouched instead of half-deleted
  • ai_jobs.pyaccept_ai_job() now 409s when studio.get_project(rec.project_id) comes back empty

Desktop UI (per-project teardown and rebuild)

  • types.ts — split the overloaded UiClip.serverId into two fields with distinct meanings: clipId (stable server clip id) and assetId (linked media, optional). Added newClipId() (crypto.randomUUID()) so a clip holds one identity from creation through every save
  • backendApi.ts — added the ClipUpsert interface and orchestratorReplaceClips(sequenceId, clips)
  • state.ts — added resetProjectState(state, history), which clears everything belonging to a single project (timeline tracks, UI state, asset bin, nextClipId, lastJobId, and both history stacks)
  • studioApp.ts
    • persistTimeline() — flattens the timeline into ClipUpsert rows and PUTs them. Runs on Save, on navigating home, and at the top of openProject() so the outgoing project is saved before its ids are cleared.
    • resetProjectView() — calls resetProjectState(), then releases everything state cannot clear on its own: playback, proxy polling, job polling, review buttons, job status text, the preview monitor (<video> and frame <img>).
    • openProject() — rebuilds the timeline from the server. Assets are loaded first so clips can take their labels from the bin.
    • Stale-response guards — every await in openProject(), plus refreshAssets() and fetchFrameForPlayhead(), now re-checks state.activeProjectId !== pid and bails.
    • addTrack() - add track and add audio uses the helper to make it persist in the timeline (similar to import media, files in these tracks will get saved and be independent to the project id)
    • shows an error message on failed saved events

Tests

  • added new tests (test_project_lifecycle.py and tests/test_job_pipeline.py)

Related

  • Issue:
  • Plaky:
  • Related PRs (optional):

Testing

  • passed all automated test cases (125 tests)
  • Manual testing to see timeline bleed fix
    • run backend and app -> make two new project files -> open one of the files, and import media which will automatically be put in the timeline -> add audio and video tracks -> go back to the home dashboard (pressing this button makes the project automatically save) -> switch to the other project file -> timeline of the current project should be empty, and switching back to the other project file will show the previously imported video and audio/video tracks
  • Deleting a project from the home page shows a DELETE /v1/projects/... HTTP/1.1" 200 OK
  • Testing save error messages
    • run backend and app -> go to a project -> import file / place a clip in timeline -> stop backend -> click save/return to home page button
    • when pressing save, an error message will pop up; when pressing the home page button, a modal with an option to stay in the project or leave project and discard changes shows up
Screenshot 2026-08-27 at 9 59 05 PM Screenshot 2026-08-27 at 9 59 26 PM

Important Notes (Optional)

  • Known limitations:
  • Blockers:
  • CI/CD issues unrelated to this PR:
  • Dependencies required for testing: running orchestrator on 127.0.0.1:8080, ffmpeg on PATH (asset import + proxy generation), and at least one importable media file

Workflow Checklist (Required)

  • Branch is up to date with dev
  • PR is from your branch → dev (no longer directly into main)
  • PR title follows convention (feat:, fix:, refactor:, etc.)
  • Plaky feature/bug name included above
  • Tagged @Team-Deepiri/support-team
  • Plaky feature moved to "Needs QA"

Review Requests

@Team-Deepiri/support-team

@calistaannelise calistaannelise added the bug Something isn't working label Aug 27, 2026
@deepiri-sorge

deepiri-sorge Bot commented Aug 27, 2026

Copy link
Copy Markdown

Sorge AI Code Review

Model: gemini-2.5-flash (gemini)
Quality Score: 8.8/10 — Good quality, minor issues worth addressing


Summary

This PR significantly enhances the application's project lifecycle management and timeline persistence. It introduces server-side storage for tracks and clips, enabling robust saving and loading of project timelines. Key improvements include atomic clip replacement, comprehensive project deletion (cascading through associated sequences, tracks, clips, assets, and AI jobs, including file cleanup), and refined memory store fallback logic to prevent data resurrection. The client-side UI has been updated to interact with these new backend capabilities, ensuring a more stable and persistent user experience. The addition of a dedicated test suite for project lifecycle is commendable and greatly improves confidence in the new features.


Issues Found

⚠️ apps/desktop-tauri/ui/src/studioApp.ts:515

The persistTimeline function logs errors to devLog but does not provide any user-facing feedback when saving the timeline fails. This could lead to data loss without the user's knowledge.
Suggestion: Implement a mechanism to display a toast notification or a similar UI element to alert the user about save failures. — For critical operations like saving user data, it's important to inform the user if the operation fails, allowing them to retry or understand the issue.

ℹ️ apps/desktop-tauri/ui/src/backendApi.ts:289

The ClipUpsert interface in apps/desktop-tauri/ui/src/backendApi.ts defines id as string, while its counterpart in services/orchestrator/app/api/schemas/studio.py defines id as UUID.
Suggestion: Consider using a type alias for UUID strings in TypeScript (e.g., type UUID = string;) and ensuring documentation clarifies the expected format. — Maintaining strict type consistency between frontend and backend schemas helps prevent subtle bugs and improves clarity. While UUIDs are often represented as strings, explicitly typing them as UUID on the backend and a UUID type (if available, or a specific string format) on the frontend can be beneficial.

ℹ️ services/orchestrator/app/db_repos.py:349

The use of __import__("json").dumps for transform_jsonb in db_repos.py is an unusual way to import and use the json module.
Suggestion: Add import json at the top of app/db_repos.py and then use json.dumps(r.get("transform_jsonb") or {}). — Standard import statements (import json) are generally preferred for readability and maintainability.


Recommendations

  • The new project deletion logic is robust; consider documenting the full cascade behavior for future reference.
  • The new test suite for project lifecycle (test_project_lifecycle.py) is excellent and a great example for future feature development.
Routing (scheduled, 1 chunk(s))
  • Rung: scheduled
  • Chunks: 1
  • Quota this run: gemini: 1/20, gpt: 0/1000, openrouter: 0/50
  • Finding adjudicator: dropped 0, demoted 1
  • Scheduler: 1 dispatch(es), 0 skipped
  • Avg complexity: 0.98
  • Provider health: gemini=66, groq=95, openrouter=90
  • Picks: gemini(ok|complexity|eff=14705|sc=0.8819)

Review generated by deepiri-sorge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant