Skip to content

feat: added download notif on both web and tauri versions#977

Open
Audatic07 wants to merge 5 commits intoCircuitVerse:mainfrom
Audatic07:export-file-notif#975
Open

feat: added download notif on both web and tauri versions#977
Audatic07 wants to merge 5 commits intoCircuitVerse:mainfrom
Audatic07:export-file-notif#975

Conversation

@Audatic07
Copy link
Contributor

@Audatic07 Audatic07 commented Feb 22, 2026

Fixes #975

Describe the changes you have made in this PR -

Added notification pop-up when using export as file, in both tauri and web versions. The tauri version lists the whole file path of the download.

Screenshots of the UI changes (If any) -

Screenshot 2026-02-22 234533 Screenshot 2026-02-22 234609

Code Understanding and AI Usage

Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?

  • No, I wrote all the code myself
  • Yes, I used AI assistance (continue below)

If you used AI assistance:

  • I have reviewed every single line of the AI-generated code
  • I can explain the purpose and logic of each function/component I added
  • I have tested edge cases and understand how the code handles them
  • I have modified the AI output to follow this project's coding standards and conventions

Explain your implementation approach:


Checklist before requesting a review

  • I have added proper PR title and linked to the issue
  • I have performed a self-review of my code
  • I can explain the purpose of every function, class, and logic block I added
  • I understand why my changes work and have tested them thoroughly
  • I have considered potential edge cases and how my code handles them
  • If it is a core feature, I have added thorough tests
  • My code follows the project's style guidelines and conventions

Note: Please check Allow edits from maintainers if you would like us to assist in the PR.

Summary by CodeRabbit

  • New Features
    • Export Project now awaits the download step and displays an immediate success message showing the file name and exact download location so users can quickly find exported files.
  • Bug Fixes / UX
    • Improved messaging to ensure confirmations appear reliably (no silent completion or suppressed messages).

@netlify
Copy link

netlify bot commented Feb 22, 2026

Deploy Preview for circuitverse ready!

Name Link
🔨 Latest commit fad605d
🔍 Latest deploy log https://app.netlify.com/projects/circuitverse/deploys/699bf4a517c03f00096d2348
😎 Deploy Preview https://deploy-preview-977--circuitverse.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 48 (🟢 up 3 from production)
Accessibility: 66 (no change from production)
Best Practices: 92 (no change from production)
SEO: 82 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Walkthrough

The export flow was changed to be asynchronous and provide user feedback. downloadFile is now async and returns a string indicating the download directory; downloadFileWeb returns "Downloads" and downloadFileDesktop returns the actual downloads directory path. A new showMessageImmediate helper was added. The ExportProject dialog now awaits downloadFile, captures the returned directory, and shows an immediate success message with the filename and download location before closing the dialog.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding download notifications to both web and Tauri versions, which matches the core objective.
Linked Issues check ✅ Passed The PR successfully implements the feature requested in #975: shows a green notification popup indicating download directory for both web and desktop versions.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the download notification feature; no unrelated modifications detected across the modified files.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/DialogBox/ExportProject.vue (1)

69-79: ⚠️ Potential issue | 🟠 Major

exportAsFile has no error handling — failures leave the user with no feedback and the dialog stuck open.

Both generateSaveData (line 71) and downloadFile (line 76) are awaited inside an async handler with no try/catch. If either rejects (e.g., Tauri FS permission denied, disk full, serialization error), the exception propagates silently out of the Vue event handler: the success showMessage is never called, the dialog is never closed, and the user has no indication of what went wrong.

🛡️ Proposed fix
 const exportAsFile = async () => {
-    let fileName = escapeHtml(fileNameInput.value) || 'untitled'
-    const circuitData = await generateSaveData(
-        projectStore.getProjectName,
-        false
-    )
-    fileName = `${fileName.replace(/[^a-z0-9]/gi, '_')}.cv`
-    const downloadDir = await downloadFile(fileName, circuitData)
-    showMessage(`"${fileName}" saved successfully to ${downloadDir}`)
-    SimulatorState.dialogBox.export_project_dialog = false
+    try {
+        let fileName = escapeHtml(fileNameInput.value) || 'untitled'
+        const circuitData = await generateSaveData(
+            projectStore.getProjectName,
+            false
+        )
+        fileName = `${fileName.replace(/[^a-z0-9]/gi, '_')}.cv`
+        const downloadDir = await downloadFile(fileName, circuitData)
+        showMessage(`"${fileName}" saved successfully to ${downloadDir}`)
+        SimulatorState.dialogBox.export_project_dialog = false
+    } catch (error) {
+        showError(`Failed to save file: ${error}`)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DialogBox/ExportProject.vue` around lines 69 - 79, Wrap the
body of exportAsFile in a try/catch: call generateSaveData and downloadFile
inside the try; on success keep the existing showMessage and set
SimulatorState.dialogBox.export_project_dialog = false; in catch, call
showMessage (or a dedicated error logger) with a descriptive error containing
the caught error message so the user sees what failed, and ensure the dialog is
closed or re-enabled (set SimulatorState.dialogBox.export_project_dialog = false
or leave it open based on UX decision) and optionally rethrow or return to stop
further execution. Reference: exportAsFile, generateSaveData, downloadFile,
showMessage, SimulatorState.dialogBox.export_project_dialog.
🧹 Nitpick comments (1)
src/components/DialogBox/ExportProject.vue (1)

44-45: Consider consolidating the two imports from the same module.

showMessage and escapeHtml are both from '#/simulator/src/utils' but imported on separate lines.

♻️ Proposed consolidation
-import { downloadFile, showMessage } from '#/simulator/src/utils'
-import { escapeHtml } from '#/simulator/src/utils'
+import { downloadFile, showMessage, escapeHtml } from '#/simulator/src/utils'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DialogBox/ExportProject.vue` around lines 44 - 45, In
ExportProject.vue consolidate the duplicate imports from '#/simulator/src/utils'
into a single import statement; specifically import downloadFile, showMessage
and escapeHtml together (remove the separate import lines) so the file uses one
combined import for those symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/DialogBox/ExportProject.vue`:
- Around line 76-77: The notification is being suppressed by showMessage's
dedupe (prevShowMessage) causing repeated exports with the same message to be
silent; in ExportProject.vue where you call downloadFile and then
showMessage(`"${fileName}" saved successfully to ${downloadDir}`), change the
call to ensure uniqueness — either append a timestamp/nonce to the message
payload (e.g., include Date.now() or a short UUID) or modify showMessage usage
to call a variant that resets prevShowMessage after displaying (update
utils.ts's showMessage or add showMessageImmediate) so repeated identical
exports (same fileName) still produce a visible notification; reference
downloadFile, showMessage and prevShowMessage when making the change.

---

Outside diff comments:
In `@src/components/DialogBox/ExportProject.vue`:
- Around line 69-79: Wrap the body of exportAsFile in a try/catch: call
generateSaveData and downloadFile inside the try; on success keep the existing
showMessage and set SimulatorState.dialogBox.export_project_dialog = false; in
catch, call showMessage (or a dedicated error logger) with a descriptive error
containing the caught error message so the user sees what failed, and ensure the
dialog is closed or re-enabled (set
SimulatorState.dialogBox.export_project_dialog = false or leave it open based on
UX decision) and optionally rethrow or return to stop further execution.
Reference: exportAsFile, generateSaveData, downloadFile, showMessage,
SimulatorState.dialogBox.export_project_dialog.

---

Duplicate comments:
In `@v1/src/components/DialogBox/ExportProject.vue`:
- Around line 44-45: Consolidate the two identical module imports from
'#/simulator/src/utils' in ExportProject.vue into a single import statement:
merge the named imports downloadFile, showMessage and escapeHtml so the file
only imports from '#/simulator/src/utils' once (referencing the existing import
occurrences for downloadFile, showMessage and escapeHtml to locate the
duplicate).
- Around line 69-79: Wrap the exportAsFile async function body in a try/catch:
inside try keep the existing steps (escapeHtml(fileNameInput.value), await
generateSaveData(...), sanitize fileName, await downloadFile(...),
showMessage(...), and set SimulatorState.dialogBox.export_project_dialog =
false); in catch log the error (console.error(err)) and surface a user-facing
message via showMessage including the error details (e.g. `Failed to save
"${fileName}": ${err.message}`) so failures are visible and avoid closing the
export dialog on failure. Reference symbols: exportAsFile, generateSaveData,
downloadFile, showMessage, SimulatorState.dialogBox.export_project_dialog,
fileNameInput.
- Around line 76-77: The call to showMessage after downloadFile is producing
duplicate notifications; update the export flow in ExportProject.vue so we only
emit a single notification: compute the message string using fileName and
downloadDir (from downloadFile(fileName, circuitData)), then guard the
showMessage call by checking a deduplication condition (e.g., compare against a
stored lastMessage or use a showMessageOnce helper) before invoking
showMessage(message). Ensure you reference the existing symbols downloadFile,
fileName, circuitData, downloadDir and showMessage so the check replaces the
direct showMessage(...) call.

In `@v1/src/simulator/src/utils.ts`:
- Around line 119-145: No code changes required — the Tauri v2 API usage in
downloadFile and downloadFileWeb is correct and matches the accepted pattern;
leave the functions downloadFile, downloadFileWeb and the desktop variant
downloadFileDesktop (and the isTauri check) as implemented and proceed with
merging this approved change.
- Around line 148-161: The downloadFileDesktop function writes an absolute path
in the Downloads directory (downloadFileDesktop, downloadDir, writeTextFile)
which requires an explicit Tauri filesystem permission; either update your Tauri
config/allowlist to permit writing to the Downloads path (add the
fs:allow-download-write or include the downloadsDirectory path in the fs
allowlist/scope) or change downloadFileDesktop to avoid absolute writes (e.g.,
use a user save dialog or write to an app-scoped directory returned by Tauri
that is already allowed). Make the change where downloadFileDesktop calls
downloadDir() and writeTextFile(...) so the runtime has the required fs
permission or the function uses a permitted/interactive save flow.

---

Nitpick comments:
In `@src/components/DialogBox/ExportProject.vue`:
- Around line 44-45: In ExportProject.vue consolidate the duplicate imports from
'#/simulator/src/utils' into a single import statement; specifically import
downloadFile, showMessage and escapeHtml together (remove the separate import
lines) so the file uses one combined import for those symbols.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/simulator/src/utils.ts (1)

125-134: ⚠️ Potential issue | 🟡 Minor

TypeScript type mismatch: JSON in downloadFile's parameter is incompatible with downloadFileWeb's narrower signature.

downloadFile accepts text: string | number | boolean | JSON, but downloadFileWeb only accepts text: string | number | boolean. Calling downloadFileWeb(filename, text) at line 132 passes a type that includes JSON, which is not assignable to downloadFileWeb's parameter — this is a TypeScript compile error.

Note: JSON in TypeScript refers to the global JSON namespace object (the one with parse/stringify), not a general "JSON-serializable value". If the intent is to allow arbitrary serializable data, a more appropriate type would be string | number | boolean | object or a custom JsonValue alias.

🛠️ Proposed fix — align types across all three functions
 export async function downloadFile(
   filename: string,
-  text: string | number | boolean | JSON,
+  text: string | number | boolean,
 ): Promise<string> {

 export function downloadFileWeb(filename: string, text: string | number | boolean): string {

 export async function downloadFileDesktop(
   filename: string,
-  text: string | number | boolean | JSON,
+  text: string | number | boolean,
 ): Promise<string> {

If callers genuinely need to pass raw objects, use text.toString() / JSON.stringify(text) at the call-site, or accept unknown and narrow before writing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/simulator/src/utils.ts` around lines 125 - 134, The function downloadFile
accepts text: string | number | boolean | JSON which is incorrect because JSON
is the global namespace and makes the call to downloadFileWeb (which expects
string|number|boolean) a type error; fix by aligning types—either change
downloadFile's signature to string | number | boolean | object or a proper
JsonValue alias (e.g., JsonValue =
string|number|boolean|null|JsonObject|JsonArray) and then serialize objects
before delegating, or narrow/serialize the incoming text in downloadFile (using
JSON.stringify for objects) before calling downloadFileWeb or
downloadFileDesktop; update the signatures of downloadFile, downloadFileWeb, and
downloadFileDesktop (or add a local narrowing step in downloadFile) to use the
same compatible type so the call downloadFileWeb(filename, text) type-checks.
src/components/DialogBox/ExportProject.vue (1)

69-79: ⚠️ Potential issue | 🟠 Major

No error handling — Tauri file-write failures are silently swallowed.

If downloadFile rejects (e.g., writeTextFile fails due to permissions, disk full, or path error on desktop), the function throws past showMessageImmediate and dialog = false. Vue doesn't surface unhandled async rejections in click handlers as visible UI, so the user sees nothing and has no way to know the save failed. Since the Tauri notification is the only feedback mechanism on desktop (no browser download indicator), this leaves users completely in the dark on failure.

🛡️ Proposed fix — wrap download in try/catch
 const exportAsFile = async () => {
     let fileName = escapeHtml(fileNameInput.value) || 'untitled'
     const circuitData = await generateSaveData(
         projectStore.getProjectName,
         false
     )
     fileName = `${fileName.replace(/[^a-z0-9]/gi, '_')}.cv`
-    const downloadDir = await downloadFile(fileName, circuitData)
-    showMessageImmediate(`"${fileName}" saved successfully to ${downloadDir}`)
-    SimulatorState.dialogBox.export_project_dialog = false
+    try {
+        const downloadDir = await downloadFile(fileName, circuitData)
+        showMessageImmediate(`"${fileName}" saved successfully to ${downloadDir}`)
+        SimulatorState.dialogBox.export_project_dialog = false
+    } catch (err) {
+        showMessageImmediate(`Failed to save "${fileName}": ${err instanceof Error ? err.message : String(err)}`)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DialogBox/ExportProject.vue` around lines 69 - 79, The
exportAsFile flow lacks error handling around the Tauri file write; wrap the
downloadFile call (and subsequent success actions: showMessageImmediate and
setting SimulatorState.dialogBox.export_project_dialog = false) in a try/catch,
calling downloadFile(fileName, circuitData) inside the try; on success keep
current behavior, but in catch call showMessageImmediate with a clear failure
message including the caught error, and do not close the dialog so the user can
retry; reference exportAsFile, fileNameInput, generateSaveData, downloadFile,
showMessageImmediate, and SimulatorState.dialogBox.export_project_dialog when
making the change.
🧹 Nitpick comments (1)
src/components/DialogBox/ExportProject.vue (1)

44-45: Consolidate the two imports from the same module.

♻️ Proposed refactor
-import { downloadFile, showMessageImmediate } from '#/simulator/src/utils'
-import { escapeHtml } from '#/simulator/src/utils'
+import { downloadFile, showMessageImmediate, escapeHtml } from '#/simulator/src/utils'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DialogBox/ExportProject.vue` around lines 44 - 45, Two
separate import statements import from '#/simulator/src/utils' (downloadFile,
showMessageImmediate and escapeHtml) so consolidate them into a single import
line; replace the two imports with one import that lists all named exports
(downloadFile, showMessageImmediate, escapeHtml) so there are no duplicate
imports in ExportProject.vue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/DialogBox/ExportProject.vue`:
- Around line 69-79: The exportAsFile flow lacks error handling around the Tauri
file write; wrap the downloadFile call (and subsequent success actions:
showMessageImmediate and setting SimulatorState.dialogBox.export_project_dialog
= false) in a try/catch, calling downloadFile(fileName, circuitData) inside the
try; on success keep current behavior, but in catch call showMessageImmediate
with a clear failure message including the caught error, and do not close the
dialog so the user can retry; reference exportAsFile, fileNameInput,
generateSaveData, downloadFile, showMessageImmediate, and
SimulatorState.dialogBox.export_project_dialog when making the change.

In `@src/simulator/src/utils.ts`:
- Around line 125-134: The function downloadFile accepts text: string | number |
boolean | JSON which is incorrect because JSON is the global namespace and makes
the call to downloadFileWeb (which expects string|number|boolean) a type error;
fix by aligning types—either change downloadFile's signature to string | number
| boolean | object or a proper JsonValue alias (e.g., JsonValue =
string|number|boolean|null|JsonObject|JsonArray) and then serialize objects
before delegating, or narrow/serialize the incoming text in downloadFile (using
JSON.stringify for objects) before calling downloadFileWeb or
downloadFileDesktop; update the signatures of downloadFile, downloadFileWeb, and
downloadFileDesktop (or add a local narrowing step in downloadFile) to use the
same compatible type so the call downloadFileWeb(filename, text) type-checks.

---

Nitpick comments:
In `@src/components/DialogBox/ExportProject.vue`:
- Around line 44-45: Two separate import statements import from
'#/simulator/src/utils' (downloadFile, showMessageImmediate and escapeHtml) so
consolidate them into a single import line; replace the two imports with one
import that lists all named exports (downloadFile, showMessageImmediate,
escapeHtml) so there are no duplicate imports in ExportProject.vue.

@Audatic07
Copy link
Contributor Author

@Nihal4777 CI is green, and Coderabbit suggestions are outside the diff. Please review :)

@naman79820
Copy link
Contributor

Tested locally and working fine.
Ready for review @Nihal4777 @ThatDeparted2061 @tachyons

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.

Feature: Feedback notification popup when completing Export as File

2 participants