Skip to content

feat(cloud): package editor assets for QtMesh Cloud upload (#684) - #719

Merged
fernandotonon merged 5 commits into
masterfrom
feat/cloud-epic-684
Jun 15, 2026
Merged

feat(cloud): package editor assets for QtMesh Cloud upload (#684)#719
fernandotonon merged 5 commits into
masterfrom
feat/cloud-epic-684

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Upload the open model and its detected dependencies to an existing QtMesh Cloud project via File → Upload to QtMesh Cloud and the account menu.
  • Add dependency resolution, project packaging/manifest generation, in-app project listing, upload progress UI, and qtmesh cloud / MCP cloud tools.
  • Run optional pre-upload scans in an isolated CLI subprocess so the live editor scene is not cleared.

Test plan

  • Sign in to QtMesh Cloud and confirm File → Upload shows a project dropdown populated from /v1/projects.
  • Upload an open FBX with sidecar textures/materials and verify only selected dependencies are sent.
  • With "Run local scan before upload" enabled, confirm the viewport scene remains intact after upload.
  • Run qtmesh cloud upload model.fbx headlessly against a signed-in session.
  • Exercise MCP cloud_list_projects, cloud_upload, and cloud_delete_project.

Made with Cursor

Summary by CodeRabbit

  • New Features
    • QtMesh Cloud integration across UI, CLI, and MCP: sign-in/out, project listing, deletion, and uploads.
    • New cloud upload workflow with a project picker, dependency detection, manifest generation, optional local scan, and cancellable progress UI with completion actions.
    • CLI: added cloud subcommands (login/logout/status/list/delete/upload) and updated command routing/help.
    • Asset scanning now supports an optional include filter for subprocess scans.
  • Tests
    • Added unit tests for dependency detection and manifest generation, including JSON path-sanitisation lint and SHA-256 output.

Add dependency-aware upload from the open model, project picker, cloud
projects dialog, CLI/MCP cloud commands, and run pre-upload scans in an
isolated subprocess so the live Ogre scene is not cleared.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 864e3e9a-6481-405c-ba4d-03b6ef2a3b7b

📥 Commits

Reviewing files that changed from the base of the PR and between 73cc3ec and 6f0b05c.

📒 Files selected for processing (3)
  • src/CloudAccountMenuButton.cpp
  • src/CloudAccountMenuButton.h
  • src/ProjectPackager.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/CloudAccountMenuButton.cpp
  • src/ProjectPackager.cpp

📝 Walkthrough

Walkthrough

Adds end-to-end QtMesh Cloud support: new cloud CLI pipeline, cloud client/session APIs, MCP cloud tools, dependency detection (DependencyResolver), packaging (ProjectPackager) with tests, UI dialogs/progress/menu updates, MainWindow integration with optional isolated scan, asset scan subprocess include-filter, and build/test wiring.

Changes

QtMesh Cloud Integration

Layer / File(s) Summary
CLI routing and build integration
src/AppLaunchHandler.cpp, src/CLIPipeline.cpp, src/CMakeLists.txt
Adds cloud subcommand to CLI allowlist and help text; CMakeLists includes all new cloud/packaging/dependency sources and headers in build.
Scan subprocess enhancement
src/AssetScanController.h, src/AssetScanController.cpp
runScanSubprocessSync accepts optional --include pattern; added runIsolatedScanJsonSync to run isolated scans with optional filtering and return JSON bytes synchronously.
Dependency detection
src/DependencyResolver.h, src/DependencyResolver.cpp
New DependencyResolver detects dependencies from files (OBJ→MTL→PNG, glTF, DAE/Collada, .mesh sidecars, RSD) and loaded Ogre scenes, canonicalizes paths, deduplicates by role, and marks existence/checked-by-default state.
Project packaging & path sanitisation
src/ProjectPackager.h, src/ProjectPackager.cpp
ProjectPackager builds PackageMetadata with resolved dependencies, common-root relative paths, computed file sizes/SHA-256 hashes, detected asset types, and optional scan summary; includes path-sanitisation linting for JSON output.
Packaging unit tests
src/ProjectPackager_test.cpp, tests/CMakeLists.txt
Four tests validate dependency detection chains (OBJ→MTL→PNG, RSD→TIM), manifest path-leakage prevention ("SECRET" redaction, /Users/ checks), SHA-256 computation, and lint passing; CMakeLists wires test target with GoogleTest discovery.
Cloud client API
src/QtMeshCloudClient.h, src/QtMeshCloudClient.cpp
Adds deleteProject(bearerToken, projectId) to send authenticated DELETE requests with HTTP status/response handling and Sentry breadcrumbs.
Async cloud session
src/QtMeshCloudSession.h, src/QtMeshCloudSession.cpp
Implements QtMeshCloudSession for background project listing and package uploads with progress signals, cancellation flag checks, project-creation HTTP 409 retry with slug adjustment, main-file role-based selection, and completion/error signaling via Qt signals.
Cloud CLI pipeline
src/CloudCLIPipeline.h, src/CloudCLIPipeline.cpp
CloudCLIPipeline implements subcommands: login (device-code sign-in or --api-key persistence), logout (with remote call + local clear), status (JSON/text sign-in state), list (project listing), delete (project removal), upload (manifest building, slug computation, asset upload, completion); exposes static run(argc, argv) dispatcher.
MCP cloud tools
src/MCPServer.h, src/MCPServer.cpp
Registers and implements MCP tools: cloud_status, cloud_login, cloud_logout, cloud_list_projects, cloud_delete_project, cloud_upload; skips Ogre/Manager init for cloud_* tools; marks cloud_upload as heavy tool; publishes tool JSON schemas with argument specs.
Cloud UI: projects dialog
src/CloudProjectsDialog.h, src/CloudProjectsDialog.cpp
CloudProjectsDialog displays projects in a QListWidget with titles/slugs, stores project URLs in item data roles, wires "Open in Browser" + double-click to URL opening via QDesktopServices, and emits Sentry breadcrumbs.
Cloud UI: upload dialog
src/CloudUploadDialog.h, src/CloudUploadDialog.cpp
CloudUploadDialog provides project selection (combo box), dependency checklist (with highlights for missing files), scan checkbox, and manifest() method that filters selected entries, filters totals, and attaches optional scan summary.
Cloud UI: upload progress
src/CloudUploadProgress.h, src/CloudUploadProgress.cpp
CloudUploadProgress widget manages upload UI: start() displays label/max, updateProgress() updates bar + filename label, finish() shows result with optional red error styling, hideProgress() resets, and emits cancelRequested signal.
Cloud account menu updates
src/CloudAccountMenuButton.h, src/CloudAccountMenuButton.cpp
Renames upload menu item to "Upload to QtMesh Cloud…"; adds setUploadEnabled(bool) to control enabled state and tooltip ("Open a model first" when disabled).
MainWindow cloud integration
src/mainwindow.h, src/mainwindow.cpp
Adds "Upload to QtMesh Cloud…" file-menu action and CloudUploadProgress status widget; introduces primaryCloudAssetPath() helper, cloudSessionForToken() session manager, showCloudProjectsDialog() projects display, updateCloudUploadActionState() enabling logic, and rewrites uploadFilesToQtMeshCloud() to use dialogs, optional isolated scan, session upload callbacks, and completion UI.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CloudCLIPipeline
  participant QtMeshCloudSession
  participant QtMeshCloudClient
  User->>CloudCLIPipeline: cloud upload file (--json or --no-scan)
  CloudCLIPipeline->>QtMeshCloudSession: create session and uploadPackage(manifest)
  QtMeshCloudSession->>QtMeshCloudClient: createProject, requestUploadUrls, upload files
  QtMeshCloudClient-->>QtMeshCloudSession: project and upload responses
  QtMeshCloudSession-->>CloudCLIPipeline: uploadFinished(ok, projectUrl)
  CloudCLIPipeline-->>User: print JSON or text result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • fernandotonon/QtMeshEditor#686: This PR implements the exact DependencyResolver with DependencyEntry struct and multi-format detection (OBJ→MTL, glTF, DAE, .mesh, RSD) plus ProjectPackager with PackageMetadata and path-sanitisation that issue #686 requests.
  • fernandotonon/QtMeshEditor#687: This PR implements the instance-based async QtMeshCloudSession with threaded upload, progress signals, cancellation, and 409-retry project creation matching issue #687 requirements.
  • fernandotonon/QtMeshEditor#684: This PR implements Epic #684 cloud integration: CLI routing (CloudCLIPipeline), MCP tools (MCPServer), async sessions, dependency resolution, packaging, and UI components spanning Slice H deliverables.

Possibly related PRs

Poem

A rabbit hops through clouds so bright,
Dependencies traced by moonlit light,
Manifests bundled, hashes spun,
Uploads racing toward the sun—🐇☁️

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides a summary of changes and a detailed test plan, but is missing the required 'Technical Details' section from the template. Add a 'Technical Details' section listing the general areas of technical change (e.g., dependency resolution, packaging, CLI integration, UI workflows) as specified in the template.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: packaging editor assets for QtMesh Cloud upload, with feature type indicator and issue reference.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cloud-epic-684

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e14d495125

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CloudCLIPipeline.cpp Outdated
QString projectName;
bool jsonOutput = false;
bool runScan = true;
for (int i = 2; i < argc; ++i) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip the upload subcommand when parsing file args

For qtmesh cloud upload model.obj, this loop starts at argv[2], which is the literal upload that CloudCLIPipeline::run just dispatched on. The positional branch then records mainFile = "upload" before it reaches the real path, so CLI uploads fail with file not found: upload or package the wrong local file if one happens to exist.

Useful? React with 👍 / 👎.

Comment thread src/CloudCLIPipeline.cpp Outdated
int cmdCloudDelete(int argc, char* argv[])
{
QString projectId;
for (int i = 2; i < argc; ++i) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip the delete subcommand when parsing project ids

For qtmesh cloud delete <project-id>, argv[2] is the literal delete, so this loop sets projectId to delete and ignores the requested id that follows. The delete command therefore cannot delete the intended project and may target the wrong id if such a project exists.

Useful? React with 👍 / 👎.

Comment thread src/CloudCLIPipeline.cpp Outdated
return 2;
}

const QString sub = QString::fromUtf8(argv[2]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Find the cloud subcommand after optional --cli

CLIPipeline::run supports editor-binary invocations like QtMeshEditor --cli cloud status and passes the original argv here, but in that form argv[2] is cloud, not status. This makes every cloud command through the editor executable fail as an unknown cloud subcommand; locate the token after cloud/skip --cli instead of hard-coding this index.

Useful? React with 👍 / 👎.

Comment thread src/mainwindow.cpp Outdated
Comment on lines +2610 to +2611
if (!m_cloudSession)
m_cloudSession = new QtMeshCloudSession(token, this);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recreate the cloud session when the token changes

When m_cloudSession already exists, this reuses the bearer token captured when the session was constructed. If a user opens cloud projects, signs out, then signs in as another account, the next project list still uses the old token (often revoked by sign-out), so project loading/upload targets the previous session until app restart; clear the session on sign-out or replace it when the stored token differs.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (5)
src/QtMeshCloudSession.cpp-108-110 (1)

108-110: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Progress math is off-by-one during file uploads.

uploadProgress(i + 1, total + 1, ...) never reaches full completion during the per-file loop and can leave UI progress short of 100% unless a finalization tick is emitted separately.

Either emit total as denominator here or add an explicit final total+1/total+1 progress update before/after completeUpload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/QtMeshCloudSession.cpp` around lines 108 - 110, The progress calculation
inside the QMetaObject::invokeMethod lambda is off-by-one: change the emitted
denominator so the per-file updates can reach 100% by either emitting
uploadProgress(i + 1, total, label) instead of uploadProgress(i + 1, total + 1,
label) in the lambda, or keep the current per-file emits but add an explicit
final emit uploadProgress(total + 1, total + 1, label) (or uploadProgress(total,
total, label) if you switch to zero-based counting) immediately before/after
calling completeUpload so the UI receives a final completion tick; update the
lambda that currently calls emit uploadProgress(...) and/or add the final emit
adjacent to completeUpload accordingly.
src/QtMeshCloudClient.cpp-661-682 (1)

661-682: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Standardize deleteProject breadcrumbs and include outcome events.

This path logs only a start breadcrumb and uses cloud.project. For a significant operation, align category naming with the repository guideline and add explicit success/failure breadcrumbs.

Suggested patch
-    SentryReporter::addBreadcrumb(QStringLiteral("cloud.project"),
+    SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
                                   QStringLiteral("QtMesh Cloud deleteProject: start"));
@@
     out.responseBodySnippet = trimSnippet(responseBody);
     out.ok = nerr == QNetworkReply::NoError && out.httpStatus >= 200 && out.httpStatus < 300;
     if (!out.ok) {
         out.errorString = nerr != QNetworkReply::NoError ? transportErr : QStringLiteral("HTTP %1").arg(out.httpStatus);
         if (!out.responseBodySnippet.isEmpty())
             out.errorString += QStringLiteral(" — ") + out.responseBodySnippet;
+        SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
+                                      QStringLiteral("QtMesh Cloud deleteProject: failure HTTP %1").arg(out.httpStatus),
+                                      QStringLiteral("warning"));
+    } else {
+        SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
+                                      QStringLiteral("QtMesh Cloud deleteProject: ok"));
     }

As per coding guidelines, significant operations should emit Sentry breadcrumbs using the prescribed category taxonomy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/QtMeshCloudClient.cpp` around lines 661 - 682, Change the single "start"
breadcrumb in deleteProject to use the standardized category and emit outcome
breadcrumbs: replace the initial SentryReporter::addBreadcrumb call that
currently uses "cloud.project" with the repository-standard category (e.g.
"cloud.project.operation") and then, after computing
out.ok/out.httpStatus/out.errorString, emit a success breadcrumb
SentryReporter::addBreadcrumb("cloud.project.operation", "deleteProject:
success") when out.ok is true, otherwise emit a failure breadcrumb
SentryReporter::addBreadcrumb("cloud.project.operation",
QStringLiteral("deleteProject: failure — %1").arg(out.errorString)) so the
operation name and error/HTTP status are recorded; keep using the existing
symbols deleteProject, out.ok, out.errorString and out.httpStatus to build the
messages.

Source: Coding guidelines

src/CloudAccountMenuButton.cpp-345-347 (1)

345-347: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

refresh() currently overrides the external upload gating state.

Line 346 re-enables upload for any signed-in user, so opening the menu can undo setUploadEnabled(false) from MainWindow and expose an action that immediately errors.

Suggested change
// src/CloudAccountMenuButton.h
+    bool m_uploadAllowedByContext = true;

// src/CloudAccountMenuButton.cpp
 void CloudAccountMenuButton::refresh()
 {
...
-    m_uploadAction->setEnabled(signedIn);
+    m_uploadAction->setEnabled(signedIn && m_uploadAllowedByContext);
...
 }

 void CloudAccountMenuButton::setUploadEnabled(bool enabled)
 {
+    m_uploadAllowedByContext = enabled;
     if (m_uploadAction)
-        m_uploadAction->setEnabled(enabled && CloudCredentialStore::hasSession());
+        m_uploadAction->setEnabled(m_uploadAllowedByContext && CloudCredentialStore::hasSession());

Also applies to: 354-362

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CloudAccountMenuButton.cpp` around lines 345 - 347, The refresh() method
currently unconditionally re-enables m_uploadAction for signed-in users and thus
can undo an external gate like MainWindow::setUploadEnabled(false); modify
refresh() so it respects the externally controlled upload state: replace the
unconditional m_uploadAction->setEnabled(signedIn) with
m_uploadAction->setEnabled(signedIn && m_uploadEnabled) (or call the existing
setter that MainWindow uses, e.g., setUploadEnabled(...)), and apply the same
guarded logic to the other refresh sites mentioned (the block around lines
354-362) so refresh never overrides an externally stored upload-enabled flag.
src/mainwindow.cpp-2374-2375 (1)

2374-2375: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Upload action enablement doesn’t match the actual upload precondition.

updateCloudUploadActionState() enables upload when entities exist, but uploadFilesToQtMeshCloud() still requires a non-empty mainAssetPath and immediately rejects otherwise. This creates a predictable enabled-but-unusable action path.

Suggested change
-    const bool hasAsset = !primaryCloudAssetPath().isEmpty()
-        || !Manager::getSingleton()->getEntities().isEmpty();
+    const bool hasAsset = !primaryCloudAssetPath().isEmpty();

Also applies to: 2655-2660

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mainwindow.cpp` around lines 2374 - 2375, The enablement check in
updateCloudUploadActionState() (hasAsset = !primaryCloudAssetPath().isEmpty() ||
!Manager::getSingleton()->getEntities().isEmpty()) doesn't match the stricter
precondition in uploadFilesToQtMeshCloud() which rejects when
primaryCloudAssetPath()/mainAssetPath is empty; make the two consistent by
updating uploadFilesToQtMeshCloud(): change its precondition to allow upload
when either primaryCloudAssetPath() is non-empty OR
Manager::getSingleton()->getEntities() is non-empty (matching the hasAsset
logic), and if primaryCloudAssetPath() is empty but entities exist, branch to
build/serialize the upload payload from the entities (or create a temporary
asset) instead of immediately rejecting; alternatively, if you prefer to require
an asset, update updateCloudUploadActionState() to use only
!primaryCloudAssetPath().isEmpty() so the action is disabled when no main
asset—pick one approach and apply it consistently in
updateCloudUploadActionState() and uploadFilesToQtMeshCloud().
src/CloudUploadProgress.cpp-28-34 (1)

28-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset visual/control state in start() before showing a new upload.

If a prior upload finished and the next one starts before hideProgress() runs, the cancel button can stay disabled and failure styling can carry over.

Suggested change
 void CloudUploadProgress::start(const QString& label, int total)
 {
+    m_cancelButton->setEnabled(true);
+    m_label->setStyleSheet({});
     m_label->setText(label);
     m_bar->setRange(0, qMax(1, total));
     m_bar->setValue(0);
     show();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CloudUploadProgress.cpp` around lines 28 - 34, CloudUploadProgress::start
currently only sets m_label and m_bar and shows the widget, so leftover state
(disabled cancel button or failure styling) can persist from a prior run; update
start() to also reset visual and control state before show(): set the cancel
button enabled (e.g., m_cancelButton->setEnabled(true)), clear any
failure/errored styling or flags used to indicate a failed upload (remove
failure CSS class or reset m_failed/m_status state), reset progress text/state
(if any), and ensure m_bar value/range are initialized as shown; this mirrors
hideProgress() cleanup so new uploads always start with a clean enabled cancel
button and no failure styling.
🧹 Nitpick comments (1)
src/AssetScanController.cpp (1)

322-334: ⚡ Quick win

Add breadcrumbs for isolated pre-upload scans.

runIsolatedScanJsonSync() is part of a user-triggered upload flow and performs a significant scan operation, but it currently emits no breadcrumb for start/failure/success.

As per coding guidelines, add SentryReporter::addBreadcrumb() for significant I/O operations with file.import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/AssetScanController.cpp` around lines 322 - 334, Add Sentry breadcrumbs
around the isolated pre-upload scan in runIsolatedScanJsonSync: before calling
runScanSubprocessSync emit a breadcrumb via SentryReporter::addBreadcrumb(...)
with category "file.import" and a message like "isolated scan started" (include
rootPath/includePattern in data); on non-ok outcome emit a failure breadcrumb
with category "file.import" and message "isolated scan failed" (include
outcome.message and any identifying inputs) and then set errorOut as now; on
success emit a success breadcrumb with category "file.import" and message
"isolated scan succeeded" (include summary info or size of outcome.jsonBytes).
Ensure the breadcrumbs do not change control flow and are added immediately
before/after the runScanSubprocessSync call and outcome handling in
runIsolatedScanJsonSync.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/CloudCLIPipeline.cpp`:
- Around line 178-182: The loops that parse arguments incorrectly start at
argv[2] (the subcommand token), so change both loops that currently read "for
(int i = 2; ..." to start at index 3 (e.g., "for (int i = 3; ...") so parsing of
projectId and filePath skips the subcommand; update the parsing logic that
assigns to projectId and filePath (the loops that check
arg.startsWith(QLatin1Char('-')) and set projectId/filePath) to iterate from i =
3 instead of i = 2 to avoid treating the subcommand itself as an argument.
- Around line 239-317: The upload flow in CloudCLIPipeline (around
ProjectPackager::buildManifest through QtMeshCloudClient::completeUpload) lacks
Sentry breadcrumbs; add SentryReporter::addBreadcrumb() calls with category
"file.export" to instrument start/failure/success: add a breadcrumb before
requesting URLs (e.g., "upload.start" with projectName and fileCount), add one
per file before calling QtMeshCloudClient::uploadFileContent (e.g.,
"file.export.start" with upload filename/fileId), add a breadcrumb on per-file
upload failure/success after uploadFileContent returns (e.g.,
"file.export.error" / "file.export.success" with fileId and errorString if any),
and add a final breadcrumb after completeUpload indicating overall
"upload.complete" or on completeUpload failure ("upload.error") including
projectUrl, project.projectSlug and errorString; use
SentryReporter::addBreadcrumb consistently for these points.

In `@src/CloudProjectsDialog.cpp`:
- Around line 31-40: Wrap the dialog open/close actions so they emit Sentry
breadcrumbs: add a small handler method (e.g.
CloudProjectsDialog::onOpenSelected()) that is invoked from the m_openButton
clicked lambda and from the m_list itemDoubleClicked connection and, inside it,
call SentryReporter::addBreadcrumb("ui.action", "cloud-projects.open") before
retrieving the selected item and calling QDesktopServices::openUrl(QUrl(url));
likewise replace the direct connect for closeButton with a lambda or a new
CloudProjectsDialog::onCloseClicked() that calls
SentryReporter::addBreadcrumb("ui.action", "cloud-projects.close") and then
QDialog::reject(); ensure the double-click connection points at the shared
onOpenSelected handler so the double-click path also emits the same breadcrumb.
- Around line 32-39: The click handler for m_openButton opens project.projectUrl
directly; instead validate the URL before opening by constructing a QUrl,
ensuring url.isValid(), url.isLocalFile() is false, and that
url.scheme().toLower() == "https" (or match an allow-list of safe schemes/hosts)
before calling QDesktopServices::openUrl(QUrl(url)); if validation fails, do not
open. Also add SentryReporter::addBreadcrumb(...) with category "ui.action" and
a message like "Open in Browser" inside that validated-open path, and add a
similar SentryReporter::addBreadcrumb(...) for the dialog Close action (e.g., in
the close/Reject/Close button handler) so user-facing actions (Open in Browser
and Close) are recorded; ensure the double-click handler delegates to the same
validated open logic so it benefits from the same checks and breadcrumb.

In `@src/CloudUploadDialog.cpp`:
- Around line 58-63: Add Sentry breadcrumbs for the user actions wired in this
dialog: call SentryReporter::addBreadcrumb(...) with category "ui.action" when
cancelButton is clicked (in the cancel connect), when m_uploadButton is clicked
(in the upload connect) and when m_projectCombo currentIndexChanged fires
(inside the existing lambda before/after calling hasSelectedProject()). Also add
a breadcrumb for the dependency detection operation (the significant scan/parse
code referenced around lines 105-109) using SentryReporter::addBreadcrumb(...)
with an appropriate category such as "file.import" or "file.export" and a short
message describing the operation; include any pertinent metadata (selected
project id/name) where available. Ensure breadcrumb text is concise and use the
same SentryReporter API everywhere.

In `@src/CloudUploadProgress.cpp`:
- Around line 24-25: Add a Sentry breadcrumb for the cancel button click in
CloudUploadProgress so the user action is recorded before emitting
cancelRequested(): update the slot or lambda connected to m_cancelButton (the
connection that currently calls CloudUploadProgress::cancelRequested) to call
SentryReporter::addBreadcrumb("ui.action", "cancel_upload" or similar short
descriptor) immediately before invoking cancelRequested(), ensuring the
breadcrumb is added even if cancelRequested() triggers early returns or thread
changes.

In `@src/DependencyResolver.cpp`:
- Around line 251-257: The code branch that handles ext == QLatin1String("obj")
currently only checks mainInfo.completeBaseName() + ".mtl" and must instead
parse the OBJ file for "mtllib" directives: open the OBJ file referenced by
mainInfo, scan lines for tokens beginning with "mtllib" (handling multiple
filenames on a line and quoted/unquoted names), for each referenced mtl name
resolve it relative to mainInfo.absolutePath() (and also treat
absolute/absolute-URL forms appropriately), then for each resolved mtlPath if
QFileInfo::exists(mtlPath) call appendEntry(out, seen, mtlPath,
QStringLiteral("material"), mainInfo.fileName()) and
collectMtlDependencies(mtlPath, out, seen); keep the existing basename.mtl probe
only as a fallback if no mtllib directives are found.

In `@src/mainwindow.cpp`:
- Around line 229-233: Add a UI breadcrumb when the user clicks the File →
Upload menu by calling SentryReporter::addBreadcrumb(...) with category
"ui.action" in the menu click handler: insert a call at the start of
MainWindow::uploadFilesToQtMeshCloud (the slot connected from
m_cloudUploadMenuAction) that creates a breadcrumb describing the action (e.g.,
"File → Upload to QtMesh Cloud") before the existing cloud.upload logging; do
the same for the other corresponding menu/toolbar handlers mentioned (the other
menu action handlers referenced in the review) so all menu clicks emit a
ui.action breadcrumb.
- Around line 2610-2611: signOutOfQtMeshCloud() must clear the cached session to
avoid using a stale bearer token: if m_cloudSession is non-null, destroy it and
set m_cloudSession = nullptr (e.g. call m_cloudSession->deleteLater();
m_cloudSession = nullptr; or delete m_cloudSession; m_cloudSession = nullptr;
depending on QObject ownership) so subsequent calls in showCloudProjectsDialog()
and the initial listing in uploadFilesToQtMeshCloud() will recreate a
QtMeshCloudSession with the new token; no other changes required to
QtMeshCloudSession or QtMeshCloudClient::fetchProjects.
- Around line 2616-2621: The lambda connected to
QtMeshCloudSession::projectsListed in MainWindow::showCloudProjectsDialog
captures local variables (loop, projects, error) by reference but the connection
is left active, risking a UAF; change the connect call to use
Qt::SingleShotConnection (add Qt::SingleShotConnection as the connection type
parameter) so the slot is invoked once and auto-disconnected, or alternatively
explicitly disconnect the signal right after loop.exec() returns; locate the
connect line referencing m_cloudSession, projectsListed, and the lambda in
MainWindow::showCloudProjectsDialog and update it to use
Qt::SingleShotConnection (or add a matching disconnect after loop.exec()).
- Around line 2605-2624: The progress dialog Cancel button isn't wired to abort
the nested QEventLoop(s) or the cloud request: connect the
QProgressDialog::canceled signal (the local progress instance) to a lambda that
calls m_cloudSession->cancel() and quits the appropriate QEventLoop (e.g. loop
and the other listLoop) so the UI unblocks immediately; additionally, update
QtMeshCloudSession::listProjects() to respect the m_canceled flag (check
m_canceled before starting work and after any blocking/waitable call and return
early or propagate cancellation to the transport) so calls to
QtMeshCloudSession::cancel() actually stop/avoid calling
QtMeshCloudClient::fetchProjects() (or otherwise abort the in-flight request)
instead of waiting for the transport timeout.

In `@src/MCPServer.cpp`:
- Around line 5151-5159: ProjectPackager::buildManifest is being called with an
empty dependency list and although ScanEngine::run()/scanReportToJsonObject
stores results in manifest.scanSummary, the discovered sidecar files are never
added to manifest.files so only the root asset is uploaded; fix this by
extracting the discovered file paths from the scan report (use ScanEngine::run
return or the JSON from ScanEngine::scanReportToJsonObject) and either (a) pass
that dependency list into ProjectPackager::buildManifest instead of {} or (b)
after building the manifest, append the discovered paths into manifest.files
(and update any metadata entries needed) so textures/materials discovered by
ScanConfig are included in the uploaded package; refer to
ProjectPackager::buildManifest, manifest.files, manifest.scanSummary,
ScanEngine::run and ScanEngine::scanReportToJsonObject when making the change.
- Around line 5070-5145: The code reads
CloudCredentialStore::loadSession().token in toolCloudLogout,
toolCloudDeleteProject, and toolCloudUpload without calling
migrateLegacySettingsIfNeeded(), causing upgraded installs with only legacy
credentials to appear signed-out; fix by calling
CloudCredentialStore::migrateLegacySettingsIfNeeded() before each token read in
MCPServer::toolCloudLogout, MCPServer::toolCloudDeleteProject, and
MCPServer::toolCloudUpload so the session is migrated before checking token
validity.
- Around line 634-638: callTool() currently gates all handlers with
ensureOgreInitialized(), which causes the new cloud handlers (toolCloudStatus,
toolCloudLogin, toolCloudLogout, toolCloudListProjects, toolCloudDeleteProject)
to fail in headless sessions; update the dispatcher so scene-dependent tools
still call ensureOgreInitialized() but the cloud management handlers bypass that
check: either (A) add an explicit whitelist in the tool dispatch logic to skip
ensureOgreInitialized() for the QStringLiteral names "cloud_status",
"cloud_login", "cloud_logout", "cloud_list_projects", "cloud_delete_project", or
(B) move the ensureOgreInitialized() call out of the global callTool() path and
into the scene-dependent handlers (e.g. those that use Ogre), leaving the
cloud_* handler methods free to run without Ogre init; implement one of these
fixes and ensure callTool() invokes the selected cloud handler methods
(toolCloudStatus, toolCloudLogin, toolCloudLogout, toolCloudListProjects,
toolCloudDeleteProject) without calling ensureOgreInitialized().

In `@src/QtMeshCloudSession.cpp`:
- Around line 57-61: When handling the existing-project branch (createNewProject
== false) in QtMeshCloudSession.cpp you must populate project.projectUrl the
same way the new-project path does so uploadFinished emits a valid URL; in the
else branch that currently sets project.ok, project.ownerSlug and
project.projectSlug, also construct and assign project.projectUrl (using the
same URL format/templating logic used elsewhere for new projects) so the
uploaded project's URL is not dropped.
- Around line 22-31: The worker lambdas capture raw this and call
QMetaObject::invokeMethod(this, ...) which can use-after-free if the session is
destroyed; change the lambda(s) that create QThread workers (the QThread::create
block that calls QtMeshCloudClient::fetchProjects and other similar worker
lambdas) to capture a QPointer<QtMeshCloudSession> (e.g.
QPointer<QtMeshCloudSession> self(this)) instead of raw this, and before calling
QMetaObject::invokeMethod or emitting signals check that self is not null (or
call invokeMethod(self.data(), ...)); alternatively implement a small QObject
worker whose lifetime is parented to the session and move the work to that
worker and invoke back using the worker or the guarded QPointer — update all
similar sites (the other worker lambdas mentioned in the review) accordingly so
no worker captures raw this or invokes on a possibly-destroyed pointer.

---

Minor comments:
In `@src/CloudAccountMenuButton.cpp`:
- Around line 345-347: The refresh() method currently unconditionally re-enables
m_uploadAction for signed-in users and thus can undo an external gate like
MainWindow::setUploadEnabled(false); modify refresh() so it respects the
externally controlled upload state: replace the unconditional
m_uploadAction->setEnabled(signedIn) with m_uploadAction->setEnabled(signedIn &&
m_uploadEnabled) (or call the existing setter that MainWindow uses, e.g.,
setUploadEnabled(...)), and apply the same guarded logic to the other refresh
sites mentioned (the block around lines 354-362) so refresh never overrides an
externally stored upload-enabled flag.

In `@src/CloudUploadProgress.cpp`:
- Around line 28-34: CloudUploadProgress::start currently only sets m_label and
m_bar and shows the widget, so leftover state (disabled cancel button or failure
styling) can persist from a prior run; update start() to also reset visual and
control state before show(): set the cancel button enabled (e.g.,
m_cancelButton->setEnabled(true)), clear any failure/errored styling or flags
used to indicate a failed upload (remove failure CSS class or reset
m_failed/m_status state), reset progress text/state (if any), and ensure m_bar
value/range are initialized as shown; this mirrors hideProgress() cleanup so new
uploads always start with a clean enabled cancel button and no failure styling.

In `@src/mainwindow.cpp`:
- Around line 2374-2375: The enablement check in updateCloudUploadActionState()
(hasAsset = !primaryCloudAssetPath().isEmpty() ||
!Manager::getSingleton()->getEntities().isEmpty()) doesn't match the stricter
precondition in uploadFilesToQtMeshCloud() which rejects when
primaryCloudAssetPath()/mainAssetPath is empty; make the two consistent by
updating uploadFilesToQtMeshCloud(): change its precondition to allow upload
when either primaryCloudAssetPath() is non-empty OR
Manager::getSingleton()->getEntities() is non-empty (matching the hasAsset
logic), and if primaryCloudAssetPath() is empty but entities exist, branch to
build/serialize the upload payload from the entities (or create a temporary
asset) instead of immediately rejecting; alternatively, if you prefer to require
an asset, update updateCloudUploadActionState() to use only
!primaryCloudAssetPath().isEmpty() so the action is disabled when no main
asset—pick one approach and apply it consistently in
updateCloudUploadActionState() and uploadFilesToQtMeshCloud().

In `@src/QtMeshCloudClient.cpp`:
- Around line 661-682: Change the single "start" breadcrumb in deleteProject to
use the standardized category and emit outcome breadcrumbs: replace the initial
SentryReporter::addBreadcrumb call that currently uses "cloud.project" with the
repository-standard category (e.g. "cloud.project.operation") and then, after
computing out.ok/out.httpStatus/out.errorString, emit a success breadcrumb
SentryReporter::addBreadcrumb("cloud.project.operation", "deleteProject:
success") when out.ok is true, otherwise emit a failure breadcrumb
SentryReporter::addBreadcrumb("cloud.project.operation",
QStringLiteral("deleteProject: failure — %1").arg(out.errorString)) so the
operation name and error/HTTP status are recorded; keep using the existing
symbols deleteProject, out.ok, out.errorString and out.httpStatus to build the
messages.

In `@src/QtMeshCloudSession.cpp`:
- Around line 108-110: The progress calculation inside the
QMetaObject::invokeMethod lambda is off-by-one: change the emitted denominator
so the per-file updates can reach 100% by either emitting uploadProgress(i + 1,
total, label) instead of uploadProgress(i + 1, total + 1, label) in the lambda,
or keep the current per-file emits but add an explicit final emit
uploadProgress(total + 1, total + 1, label) (or uploadProgress(total, total,
label) if you switch to zero-based counting) immediately before/after calling
completeUpload so the UI receives a final completion tick; update the lambda
that currently calls emit uploadProgress(...) and/or add the final emit adjacent
to completeUpload accordingly.

---

Nitpick comments:
In `@src/AssetScanController.cpp`:
- Around line 322-334: Add Sentry breadcrumbs around the isolated pre-upload
scan in runIsolatedScanJsonSync: before calling runScanSubprocessSync emit a
breadcrumb via SentryReporter::addBreadcrumb(...) with category "file.import"
and a message like "isolated scan started" (include rootPath/includePattern in
data); on non-ok outcome emit a failure breadcrumb with category "file.import"
and message "isolated scan failed" (include outcome.message and any identifying
inputs) and then set errorOut as now; on success emit a success breadcrumb with
category "file.import" and message "isolated scan succeeded" (include summary
info or size of outcome.jsonBytes). Ensure the breadcrumbs do not change control
flow and are added immediately before/after the runScanSubprocessSync call and
outcome handling in runIsolatedScanJsonSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c37d3274-c283-4445-8fb3-a69ea558c1f8

📥 Commits

Reviewing files that changed from the base of the PR and between 937d619 and e14d495.

📒 Files selected for processing (28)
  • src/AppLaunchHandler.cpp
  • src/AssetScanController.cpp
  • src/AssetScanController.h
  • src/CLIPipeline.cpp
  • src/CMakeLists.txt
  • src/CloudAccountMenuButton.cpp
  • src/CloudAccountMenuButton.h
  • src/CloudCLIPipeline.cpp
  • src/CloudCLIPipeline.h
  • src/CloudProjectsDialog.cpp
  • src/CloudProjectsDialog.h
  • src/CloudUploadDialog.cpp
  • src/CloudUploadDialog.h
  • src/CloudUploadProgress.cpp
  • src/CloudUploadProgress.h
  • src/DependencyResolver.cpp
  • src/DependencyResolver.h
  • src/MCPServer.cpp
  • src/MCPServer.h
  • src/ProjectPackager.cpp
  • src/ProjectPackager.h
  • src/ProjectPackager_test.cpp
  • src/QtMeshCloudClient.cpp
  • src/QtMeshCloudClient.h
  • src/QtMeshCloudSession.cpp
  • src/QtMeshCloudSession.h
  • src/mainwindow.cpp
  • src/mainwindow.h

Comment thread src/CloudCLIPipeline.cpp Outdated
Comment thread src/CloudCLIPipeline.cpp
Comment thread src/CloudProjectsDialog.cpp Outdated
Comment on lines +32 to +39
connect(m_openButton, &QPushButton::clicked, this, [this]() {
const QListWidgetItem* item = m_list->currentItem();
if (!item)
return;
const QString url = item->data(Qt::UserRole + 1).toString();
if (!url.isEmpty())
QDesktopServices::openUrl(QUrl(url));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and show relevant sections
ls -la src/CloudProjectsDialog.cpp
sed -n '1,120p' src/CloudProjectsDialog.cpp
sed -n '120,260p' src/CloudProjectsDialog.cpp

# Check for breadcrumb instrumentation in this file
rg -n "SentryReporter::addBreadcrumb|addBreadcrumb\\(" src/CloudProjectsDialog.cpp

# Check where URL data role is set/used
rg -n "Qt::UserRole \\+ 1|UserRole \\+ 1|projectUrl|data\\(Qt::UserRole|setProjects\\(" src/CloudProjectsDialog.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 2191


Harden external URL handling and add Sentry breadcrumbs for dialog actions

project.projectUrl is API-fed and is opened directly; validate it with an allow-list (prefer https) before calling QDesktopServices::openUrl(...).

Also add SentryReporter::addBreadcrumb() (category: ui.action) for the dialog’s user-facing actions (at least Close and “Open in Browser”; double-click should be covered by the same open handler).

🔧 Proposed fix
-    connect(m_openButton, &QPushButton::clicked, this, [this]() {
+    connect(m_openButton, &QPushButton::clicked, this, [this]() {
         const QListWidgetItem* item = m_list->currentItem();
         if (!item)
             return;
         const QString url = item->data(Qt::UserRole + 1).toString();
-        if (!url.isEmpty())
-            QDesktopServices::openUrl(QUrl(url));
+        const QUrl parsed = QUrl::fromUserInput(url);
+        if (parsed.isValid() && parsed.scheme().compare(QStringLiteral("https"), Qt::CaseInsensitive) == 0)
+            QDesktopServices::openUrl(parsed);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CloudProjectsDialog.cpp` around lines 32 - 39, The click handler for
m_openButton opens project.projectUrl directly; instead validate the URL before
opening by constructing a QUrl, ensuring url.isValid(), url.isLocalFile() is
false, and that url.scheme().toLower() == "https" (or match an allow-list of
safe schemes/hosts) before calling QDesktopServices::openUrl(QUrl(url)); if
validation fails, do not open. Also add SentryReporter::addBreadcrumb(...) with
category "ui.action" and a message like "Open in Browser" inside that
validated-open path, and add a similar SentryReporter::addBreadcrumb(...) for
the dialog Close action (e.g., in the close/Reject/Close button handler) so
user-facing actions (Open in Browser and Close) are recorded; ensure the
double-click handler delegates to the same validated open logic so it benefits
from the same checks and breadcrumb.

Comment thread src/CloudUploadDialog.cpp Outdated
Comment on lines +58 to +63
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(m_uploadButton, &QPushButton::clicked, this, &QDialog::accept);
connect(m_projectCombo, &QComboBox::currentIndexChanged, this, [this](int) {
m_uploadButton->setEnabled(hasSelectedProject());
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Instrument upload dialog actions and dependency scan with breadcrumbs.

This adds a major user-facing workflow, but no ui.action breadcrumbs are emitted for cancel/upload/selection interactions, and no significant-operation breadcrumb is emitted for dependency detection.

🔧 Proposed fix
+#include "SentryReporter.h"
+
-    connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
-    connect(m_uploadButton, &QPushButton::clicked, this, &QDialog::accept);
+    connect(cancelButton, &QPushButton::clicked, this, [this]() {
+        SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
+                                      QStringLiteral("cloud.upload_dialog.cancel"));
+        reject();
+    });
+    connect(m_uploadButton, &QPushButton::clicked, this, [this]() {
+        SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
+                                      QStringLiteral("cloud.upload_dialog.confirm_upload"));
+        accept();
+    });
     connect(m_projectCombo, &QComboBox::currentIndexChanged, this, [this](int) {
+        SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
+                                      QStringLiteral("cloud.upload_dialog.project_changed"));
         m_uploadButton->setEnabled(hasSelectedProject());
     });
@@
 void CloudUploadDialog::rebuildDependencyList()
 {
     m_dependencyList->clear();
+    SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                  QStringLiteral("cloud.upload_dialog.detect_dependencies"));
     m_dependencies = DependencyResolver::detect(m_mainAssetPath);

As per coding guidelines: “Add Sentry breadcrumbs for all user-facing actions and significant operations using SentryReporter::addBreadcrumb() with categories: ui.actionfile.import/file.export for I/O”.

Also applies to: 105-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CloudUploadDialog.cpp` around lines 58 - 63, Add Sentry breadcrumbs for
the user actions wired in this dialog: call SentryReporter::addBreadcrumb(...)
with category "ui.action" when cancelButton is clicked (in the cancel connect),
when m_uploadButton is clicked (in the upload connect) and when m_projectCombo
currentIndexChanged fires (inside the existing lambda before/after calling
hasSelectedProject()). Also add a breadcrumb for the dependency detection
operation (the significant scan/parse code referenced around lines 105-109)
using SentryReporter::addBreadcrumb(...) with an appropriate category such as
"file.import" or "file.export" and a short message describing the operation;
include any pertinent metadata (selected project id/name) where available.
Ensure breadcrumb text is concise and use the same SentryReporter API
everywhere.

Source: Coding guidelines

Comment thread src/MCPServer.cpp
Comment on lines +5151 to +5159
PackageMetadata manifest = ProjectPackager::buildManifest(filePath, {}, projectName);
const bool runScan = !args.contains(QStringLiteral("scan")) || args.value(QStringLiteral("scan")).toBool(true);
if (runScan) {
ScanConfig config;
config.roots = {QFileInfo(filePath).absolutePath()};
config.includePatterns = {QFileInfo(filePath).fileName()};
manifest.scanSummary = ScanEngine::scanReportToJsonObject(
ScanEngine::run(config, config.roots.first()));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

cloud_upload currently uploads only the root asset.

ProjectPackager::buildManifest(filePath, {}, projectName) hard-codes an empty dependency list, and the optional scan result is only stored in manifest.scanSummary. None of the discovered sidecar files are added back into manifest.files, so textures/materials referenced by the asset never make it into the uploaded package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/MCPServer.cpp` around lines 5151 - 5159, ProjectPackager::buildManifest
is being called with an empty dependency list and although
ScanEngine::run()/scanReportToJsonObject stores results in manifest.scanSummary,
the discovered sidecar files are never added to manifest.files so only the root
asset is uploaded; fix this by extracting the discovered file paths from the
scan report (use ScanEngine::run return or the JSON from
ScanEngine::scanReportToJsonObject) and either (a) pass that dependency list
into ProjectPackager::buildManifest instead of {} or (b) after building the
manifest, append the discovered paths into manifest.files (and update any
metadata entries needed) so textures/materials discovered by ScanConfig are
included in the uploaded package; refer to ProjectPackager::buildManifest,
manifest.files, manifest.scanSummary, ScanEngine::run and
ScanEngine::scanReportToJsonObject when making the change.

Comment thread src/MCPServer.cpp Outdated
Comment thread src/ProjectPackager.cpp
Comment thread src/QtMeshCloudSession.cpp Outdated
Comment thread src/QtMeshCloudSession.cpp
Link cloud upload sources into unit tests, fix cloud CLI argument parsing,
harden project packaging paths, guard async cloud session callbacks, reuse
sessions across auth changes, and run MCP pre-upload scans in a subprocess.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/CloudAccountMenuButton.cpp`:
- Line 346: The refresh() method currently force-enables m_uploadAction
(m_uploadAction->setEnabled(true)), which overrides setUploadEnabled(hasAsset)
and MainWindow::updateCloudUploadActionState(); remove that unconditional enable
(or change it to respect the current upload-enabled state) so refresh() does not
re-enable the action—locate the m_uploadAction->setEnabled(true) call in
refresh() and delete it (or replace it with logic that queries the existing
upload-enabled flag or calls setUploadEnabled(hasAsset) instead).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ef82fef-f4b7-4fd2-b750-27dbd308bb8b

📥 Commits

Reviewing files that changed from the base of the PR and between e14d495 and 117caf7.

📒 Files selected for processing (9)
  • src/CloudAccountMenuButton.cpp
  • src/CloudCLIPipeline.cpp
  • src/DependencyResolver.cpp
  • src/MCPServer.cpp
  • src/ProjectPackager.cpp
  • src/QtMeshCloudSession.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
  • tests/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/mainwindow.h
  • src/MCPServer.cpp
  • src/DependencyResolver.cpp
  • src/QtMeshCloudSession.cpp
  • src/ProjectPackager.cpp
  • src/mainwindow.cpp
  • src/CloudCLIPipeline.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/CloudAccountMenuButton.cpp`:
- Line 346: The refresh() method currently force-enables m_uploadAction
(m_uploadAction->setEnabled(true)), which overrides setUploadEnabled(hasAsset)
and MainWindow::updateCloudUploadActionState(); remove that unconditional enable
(or change it to respect the current upload-enabled state) so refresh() does not
re-enable the action—locate the m_uploadAction->setEnabled(true) call in
refresh() and delete it (or replace it with logic that queries the existing
upload-enabled flag or calls setUploadEnabled(hasAsset) instead).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ef82fef-f4b7-4fd2-b750-27dbd308bb8b

📥 Commits

Reviewing files that changed from the base of the PR and between e14d495 and 117caf7.

📒 Files selected for processing (9)
  • src/CloudAccountMenuButton.cpp
  • src/CloudCLIPipeline.cpp
  • src/DependencyResolver.cpp
  • src/MCPServer.cpp
  • src/ProjectPackager.cpp
  • src/QtMeshCloudSession.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
  • tests/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/mainwindow.h
  • src/MCPServer.cpp
  • src/DependencyResolver.cpp
  • src/QtMeshCloudSession.cpp
  • src/ProjectPackager.cpp
  • src/mainwindow.cpp
  • src/CloudCLIPipeline.cpp
🛑 Comments failed to post (1)
src/CloudAccountMenuButton.cpp (1)

346-346: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid overriding upload availability in refresh()

Line 346 force-enables m_uploadAction, which undoes setUploadEnabled(hasAsset) whenever the menu opens. For signed-in users with no asset, this makes the action clickable again despite the intended disabled state from MainWindow::updateCloudUploadActionState().

Suggested fix
@@
-    m_openProjectsAction->setEnabled(signedIn);
-    m_uploadAction->setEnabled(true);
+    m_openProjectsAction->setEnabled(signedIn);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CloudAccountMenuButton.cpp` at line 346, The refresh() method currently
force-enables m_uploadAction (m_uploadAction->setEnabled(true)), which overrides
setUploadEnabled(hasAsset) and MainWindow::updateCloudUploadActionState();
remove that unconditional enable (or change it to respect the current
upload-enabled state) so refresh() does not re-enable the action—locate the
m_uploadAction->setEnabled(true) call in refresh() and delete it (or replace it
with logic that queries the existing upload-enabled flag or calls
setUploadEnabled(hasAsset) instead).

fernandotonon and others added 3 commits June 15, 2026 09:47
Skip Ogre scene dependency probing unless a live editor session exists so
unit tests do not crash after earlier Manager singleton use, and instrument
cloud project/upload dialogs with Sentry ui.action breadcrumbs.

Co-authored-by: Cursor <cursoragent@cursor.com>
…tate

ProjectPackager no longer walks the Ogre scene during manifest building,
which fixes the ProjectPackager unit-test segfault on CI. CloudAccountMenuButton
refresh now respects the asset availability set by setUploadEnabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
jsonPassesPathSanitisationLint iterated a temporary QJsonObject twice,
which segfaulted ProjectPackager.SanitisedManifestJson on Linux CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

Copy link
Copy Markdown

@fernandotonon
fernandotonon merged commit 5caa085 into master Jun 15, 2026
20 checks passed
@fernandotonon
fernandotonon deleted the feat/cloud-epic-684 branch June 15, 2026 19:12
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.

1 participant