Add per-adventure "Sync from library" for scripts

Adventure scripts are frozen snapshots taken at creation. Add an opt-in
way to pull the latest library code into a live adventure:

- AdventureScript.source_script_id links a copy to its library Script
  (migration 24; set at adventure creation)
- list endpoint flags out_of_date by diffing copy vs library
- POST /adventures/{id}/scripts/{sid}/sync overwrites the copy's code,
  preserving enabled/position/script_state
- legacy copies with no link fall back to a name match, then adopt the link
- Play page shows a Sync button only when a copy differs from its library

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
This commit is contained in:
parththakkar106
2026-07-21 00:56:01 +05:30
co-authored by Claude Opus 4.8
parent 94db6e055b
commit 328ae8942d
6 changed files with 110 additions and 3 deletions
+10 -2
View File
@@ -205,13 +205,21 @@ class Script(Base):
class AdventureScript(Base):
"""A script copied into an adventure at creation, so library edits don't
change running adventures. `state` lives on Adventure.script_state (one
shared state per adventure, as in AI Dungeon)."""
change running adventures unless the player explicitly re-syncs it from
`source_script_id`. `state` lives on Adventure.script_state (one shared
state per adventure, as in AI Dungeon)."""
__tablename__ = "adventure_scripts"
id: Mapped[int] = mapped_column(primary_key=True)
adventure_id: Mapped[int] = mapped_column(ForeignKey("adventures.id", ondelete="CASCADE"))
# The library Script this copy was made from, so it can be re-synced on
# demand. NULL for legacy copies (predate this column) and demo-derived
# ones whose source isn't owned by the player — those fall back to a
# name match, or simply aren't syncable.
source_script_id: Mapped[int | None] = mapped_column(
ForeignKey("scripts.id", ondelete="SET NULL"), nullable=True
)
position: Mapped[int] = mapped_column(Integer, default=0)
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
name: Mapped[str] = mapped_column(String(200), default="Untitled Script")