Skip to content

Conversation

@nighca
Copy link
Collaborator

@nighca nighca commented Feb 9, 2026

close #2815.

@gemini-code-assist
Copy link

Summary of Changes

Hello @nighca, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the project's architecture by abstracting the core Project interface and delegating persistence concerns to dedicated helper classes. This change enhances modularity, testability, and reusability throughout the editor components, particularly in how projects are loaded, saved, and how history operations are managed.

Highlights

  • Abstracted Project Interface: Introduced the IProject interface to define a contract for core project operations, decoupling the Project class from specific persistence mechanisms like cloud storage, local cache, and XBP file handling.
  • Dedicated Persistence Helper Classes: Created CloudHelper, LocalHelper, and XbpHelper classes to encapsulate the logic for loading and saving projects to the cloud, local storage, and XBP files, respectively. The Project class no longer contains these persistence methods directly.
  • Centralized History Management: The History object, which tracks undo/redo actions, is now managed by the EditorState and accessed consistently via editorCtx.state.history across numerous components. This centralizes history operations and improves component reusability.
  • Refactored Editing Class: The Editing class, responsible for auto-saving and managing project state, has been updated to accept injected CloudHelper and ILocalCacheHelper instances, making it more modular and testable. It also now directly owns the History instance.
  • Streamlined Project Class: The Project class has been simplified by removing its direct persistence methods and making loadMetadata public. Its constructor now explicitly takes owner and name parameters, reflecting its role as a data model rather than a persistence manager.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • spx-gui/src/components/agent-copilot/CopilotProvider.vue
    • Imported CloudHelper.
    • Replaced direct project.saveToCloud() calls with new CloudHelper().save(project).
    • Updated project loading from cloud to use new Project(owner, name) and new CloudHelper().load(p, ...).
  • spx-gui/src/components/asset/index.ts
    • Introduced useEditorCtx() to access editor context.
    • Migrated history operations from project.history.doAction(...) to editorCtx.state.history.doAction(...) in various asset-related functions.
  • spx-gui/src/components/asset/library/AssetLibraryModal.vue
    • Imported useEditorCtx().
    • Updated history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/asset/scratch/LoadFromScratch.vue
    • Imported useEditorCtx().
    • Updated history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/copilot/CopilotRoot.vue
    • Imported CloudHelper.
    • Updated project instantiation to new Project(owner, name) and cloud loading to new CloudHelper().load(p, ...).
  • spx-gui/src/components/copilot/custom-elements/CodeBlock.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/copilot/custom-elements/CodeChange.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/ProjectEditor.vue
    • Imported CloudHelper.
    • Replaced project.value.saveToCloud() with new CloudHelper().save(project.value).
  • spx-gui/src/components/editor/code-editor/FormatButton.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/code-editor/code-editor.ts
    • Added History type import.
    • Modified CodeEditor constructor to accept a History instance.
    • Updated createTextDocument to accept and pass a History instance.
  • spx-gui/src/components/editor/code-editor/context.ts
    • Updated useProvideCodeEditorCtx to pass editorState.history to the CodeEditor constructor.
  • spx-gui/src/components/editor/code-editor/text-document.ts
    • Added History type import.
    • Refactored CodeOwnerStage and CodeOwnerSprite to depend on History instead of Project.
    • Updated setCode methods to use this.history.doAction.
    • Modified createTextDocument to accept and pass a History instance to code owners.
  • spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/common/config/sprite/SpriteDirection.vue
    • Imported useEditorCtx().
    • Updated spriteContext to provide history: editorCtx.state.history.
  • spx-gui/src/components/editor/common/config/sprite/SpritePhysics.vue
    • Imported useEditorCtx().
    • Updated applyPhysicsModeToSprite to use history: editorCtx.state.history.
  • spx-gui/src/components/editor/common/config/sprite/SpritePositionSize.vue
    • Imported useEditorCtx().
    • Updated spriteContext to provide history: editorCtx.state.history.
  • spx-gui/src/components/editor/common/config/sprite/SpriteVisible.vue
    • Imported useEditorCtx().
    • Updated spriteContext to provide history: editorCtx.state.history.
  • spx-gui/src/components/editor/common/config/utils.ts
    • Modified wrapUpdateHandler to accept history: History instead of project: Project.
  • spx-gui/src/components/editor/common/viewer/SpriteNode.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/editing.test.ts
    • Introduced IProject, ILocalCacheHelper, CloudHelper types for mocking.
    • Updated mockProject to remove direct persistence methods.
    • Refactored makeEditing to accept cloudHelper and localCacheHelper.
    • Updated tests to use localCacheHelper.saveToLocalCache instead of project.saveToLocalCache.
  • spx-gui/src/components/editor/editing.ts
    • Defined ILocalCacheHelper interface and LocalCacheHelper class.
    • Refactored Saving constructor to use IProject, CloudHelper, ILocalCacheHelper.
    • Updated Saving to use localCacheHelper.save and cloudHelper.save.
    • Refactored Editing constructor to accept mode, IProject, CloudHelper, ILocalCacheHelper, isOnline.
    • Added history: History property to Editing class.
  • spx-gui/src/components/editor/editor-state.test.ts
    • Updated tests to use editorState.history.doAction(...) and editorState.history.undo().
  • spx-gui/src/components/editor/editor-state.ts
    • Imported IProject, Metadata, CloudHelper, Files, EditingMode.
    • Introduced ProjectWithGens class implementing IProject for future extensibility.
    • Refactored EditorState constructor to use CloudHelper and ILocalCacheHelper.
    • Added a history getter to EditorState that returns this.editing.history.
  • spx-gui/src/components/editor/map-editor/MapLayerSortInput.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/map-editor/MapPhysicsInput.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/map-editor/MapSizeInput.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/map-editor/map-viewer/MapViewer.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/navbar/EditorNavbar.vue
    • Imported XbpHelper and useEditorCtx().
    • Introduced xbpHelper = new XbpHelper().
    • Updated XBP file loading/saving to use xbpHelper.load and xbpHelper.save.
    • Changed undo/redo actions to use editorCtx.state.history.getUndoAction() and editorCtx.state.history.redo().
  • spx-gui/src/components/editor/panels/sound/SoundsPanel.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/preview/stage-viewer/StageViewer.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/preview/stage-viewer/widgets/MonitorNode.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sound/SoundEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sound/SoundItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sound/SoundRecorder.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/AnimationEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/AnimationItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/AnimationRemoveModal.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/CostumeItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/CostumesEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/SpriteCollisionEditor.vue
    • Imported useEditorCtx().
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/SpriteItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/SpriteList.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/animation/DurationEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/animation/SoundEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/sprite/animation/state/BoundStateEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/backdrop/BackdropItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/backdrop/BackdropModeSelector.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/backdrop/BackdropsEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/widget/WidgetItem.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/widget/WidgetsEditor.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/editor/stage/widget/detail/MonitorDetail.vue
    • Changed history actions to use editorCtx.state.history.doAction(...).
  • spx-gui/src/components/project/ProjectCreateModal.vue
    • Imported XbpHelper and CloudHelper.
    • Updated project loading from XBP file to new XbpHelper().load(project, ...).
    • Updated project saving to cloud to new CloudHelper().save(project).
    • Modified Project constructor to accept username, projectName.
  • spx-gui/src/components/project/ProjectPublishModal.vue
    • Imported CloudHelper.
    • Updated project saving to cloud to new CloudHelper().save(project).
  • spx-gui/src/components/project/index.ts
    • Imported CloudHelper.
    • Updated project saving to cloud to new CloudHelper().save(project).
  • spx-gui/src/models/common/cloud.ts
    • Introduced and implemented the CloudHelper class to encapsulate cloud load/save logic.
  • spx-gui/src/models/common/local.ts
    • Introduced and implemented the LocalHelper class to encapsulate local storage load/save/clear logic.
  • spx-gui/src/models/common/xbp.ts
    • Introduced and implemented the XbpHelper class to encapsulate XBP file load/save logic.
  • spx-gui/src/models/project/history.ts
    • Modified History constructor to accept IProject instead of Project.
  • spx-gui/src/models/project/index.test.ts
    • Removed tests related to direct Project.saveToCloud and Project.saveToLocalCache calls.
  • spx-gui/src/models/project/index.ts
    • Removed direct imports of cloudHelper, localHelper, xbpHelper.
    • Defined IProject interface.
    • Made Project.loadMetadata public.
    • Removed direct persistence methods (loadXbpFile, saveToCloud, etc.) from Project class.
    • Updated Project.load to accept an optional signal.
  • spx-gui/src/pages/community/project.vue
    • Imported CloudHelper.
    • Updated project instantiation to new Project(props.owner, props.name) and cloud loading to new CloudHelper().load(p, ...).
  • spx-gui/src/pages/editor/index.vue
    • Imported LocalHelper, LocalCacheHelper, CloudHelper.
    • Initialized localCacheHelper and cloudHelper.
    • Updated EditorState constructor to use new helper instances.
    • Changed local project loading to new LocalHelper().load(localProject, ...).
    • Changed cloud project loading to new CloudHelper().load(newProject, ...).
    • Modified Project constructor to accept ownerName, projectName.
  • spx-gui/src/widgets/spx-runner/SpxRunner.ce.vue
    • Imported CloudHelper.
    • Updated project instantiation to new Project(owner, name) and cloud loading to new CloudHelper().load(newProject, ...).
Activity
  • The pull request introduces a significant architectural refactoring by abstracting the Project interface and centralizing persistence and history management.
  • New helper classes (CloudHelper, LocalHelper, XbpHelper) were created to handle specific persistence concerns, reducing the responsibilities of the core Project class.
  • Numerous Vue components and utility functions were updated to reflect the new pattern of accessing history via editorCtx.state.history and using the new helper classes for project persistence operations.
  • The Editing class, responsible for auto-saving, was refactored to depend on the new helper classes, improving its modularity and testability.
  • The Project class itself was streamlined, removing direct persistence methods and focusing on its core data model.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant refactoring of project persistence and history management. It extracts cloud and local cache operations into dedicated CloudHelper and LocalHelper classes, and centralizes project history management by moving history from the Project model to the EditorState and making it accessible via editorCtx.state.history. This change impacts numerous components, where direct calls to project.saveToCloud() or project.history.doAction() are replaced with calls to the new helper classes or editorCtx.state.history.doAction(). The Editing class and its related tests are updated to reflect these new helper dependencies. Additionally, the Project constructor now accepts owner and name parameters, and IProject and ILocalCacheHelper interfaces are introduced. A review comment highlights a potential vulnerability in the createProject function, which saves to the cloud without user confirmation, making it susceptible to Indirect Prompt Injection. Another comment notes the removal of a test case that checked for errors when saving a disposed project, suggesting that the new implementation might not handle disposed projects correctly during export/save operations.


try {
await project.saveToCloud()
await new CloudHelper().save(project)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The createProject function, which is registered as an MCP tool, performs a side effect (saving a new project to the cloud) without requiring user confirmation. This makes the application vulnerable to Indirect Prompt Injection, where a malicious project or asset could trick the LLM into creating unwanted projects on the user's account. It is recommended to implement a confirmation dialog before executing MCP tools with side effects.

Comment on lines 92 to -102

it('should throw an error when saving a disposed project', async () => {
const project = makeProject()
const saveToLocalCacheMethod = vi.spyOn(project, 'saveToLocalCache' as any)

project.dispose()

await expect(project.saveToCloud()).rejects.toThrow('disposed')

await expect((project as any).saveToLocalCache('key')).rejects.toThrow('disposed')
expect(saveToLocalCacheMethod).toHaveBeenCalledWith('key')
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test case, which verifies that saving a disposed project throws an error, has been removed. The new implementation using helper classes doesn't seem to check if a project is disposed before exporting/saving. This could lead to unexpected behavior or errors if a disposed project is saved. Please consider re-adding this test and implementing a check for isDisposed in the Project.export method to prevent this.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Abstract project interface for reusable editor components

1 participant