feat: Native File Associations (.storycraft/.scst) and Single-Instance behavior#87
Conversation
- Register .storycraft and .scst file extensions in tauri.conf.json - Add tauri-plugin-deep-link for protocol handling - Implement RunEvent::Opened and RunEvent::SecondInstance handlers in lib.rs - Create services/tauriDeepLink.ts for frontend file opening - Wire deep link handler into App.tsx - Add unit tests for tauriDeepLink service - Update docs/TAURI-CI.md with file association documentation P0-4: Native File Associations + Single-Instance behavior for professional desktop UX
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| dispatch( | ||
| statusActions.addNotification({ | ||
| type: 'error', | ||
| title: 'Failed to open project file', |
There was a problem hiding this comment.
Suggestion: Replace this hardcoded notification title with an i18n translation key and add that key across all supported locale bundles. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The code emits a user-facing notification with a hardcoded English title instead of using the app's translation system. This is a real localization violation because the repository uses i18n keys elsewhere for UI text.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/tauriDeepLink.ts
**Line:** 87:87
**Comment:**
*Custom Rule: Replace this hardcoded notification title with an i18n translation key and add that key across all supported locale bundles.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| statusActions.addNotification({ | ||
| type: 'error', | ||
| title: 'Failed to open project file', | ||
| description: resultAction.error?.message ?? 'Unknown error', |
There was a problem hiding this comment.
Suggestion: Replace the hardcoded fallback error text with a translated string key so the default message is localized. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The fallback message is hardcoded English shown to users when no error message is present. That makes it a genuine i18n violation under the stated localization rule.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/tauriDeepLink.ts
**Line:** 88:88
**Comment:**
*Custom Rule: Replace the hardcoded fallback error text with a translated string key so the default message is localized.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| dispatch( | ||
| statusActions.addNotification({ | ||
| type: 'error', | ||
| title: 'Failed to open project file', |
There was a problem hiding this comment.
Suggestion: Localize this second hardcoded notification title through the translation-key flow to keep error messaging consistent with UI i18n rules. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
This is the same hardcoded user-facing title in the error-handling path. It is still untranslated and therefore violates the localization rule for UI strings.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/tauriDeepLink.ts
**Line:** 97:97
**Comment:**
*Custom Rule: Localize this second hardcoded notification title through the translation-key flow to keep error messaging consistent with UI i18n rules.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| useEffect(() => { | ||
| void initTauriDeepLink(dispatch); | ||
| }, [dispatch]); |
There was a problem hiding this comment.
Suggestion: The deep-link initialization effect ignores the async cleanup function returned by initTauriDeepLink, so listener teardown never runs on unmount/re-mount. In React StrictMode this can register duplicate listeners and trigger repeated imports/notifications; capture the returned cleanup and invoke it in the effect cleanup. [missing cleanup]
Severity Level: Major ⚠️
- ⚠️ Deep-link events trigger duplicate project import attempts.
- ⚠️ Users may see duplicate error notifications for one file.Steps of Reproduction ✅
1. The application entrypoint `index.tsx` at line 246 renders `<App isNewUser={isNewUser}
/>` inside `<React.StrictMode>` (see `/workspace/StoryCraft-Studio/index.tsx:246-250`),
enabling double-invocation of React effects in development.
2. On initial mount of `App` in `App.tsx`, the deep-link effect at lines 495–497
(`useEffect(() => { void initTauriDeepLink(dispatch); }, [dispatch]);`) runs and calls
`initTauriDeepLink(dispatch)` once.
3. `initTauriDeepLink` in `services/tauriDeepLink.ts` at lines 25–36 registers a listener
via `listen('deep-link://new-url', async (event) => { ... })`, storing an `unlisten`
function but returning a cleanup function that is never wired back into the React effect
(no `return` from the `useEffect`).
4. Under React StrictMode, `App` is unmounted and remounted; because the effect has no
cleanup, the previously registered listener remains active while a new listener is added
on re-mount, so when a `.storycraft` file is opened and the deep-link plugin emits
`deep-link://new-url`, multiple handlers fire and dispatch `importProjectThunk` and
`statusActions.addNotification` multiple times.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** App.tsx
**Line:** 495:497
**Comment:**
*Missing Cleanup: The deep-link initialization effect ignores the async cleanup function returned by `initTauriDeepLink`, so listener teardown never runs on unmount/re-mount. In React StrictMode this can register duplicate listeners and trigger repeated imports/notifications; capture the returned cleanup and invoke it in the effect cleanup.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const urls = Array.isArray(event.payload) ? event.payload : [event.payload]; | ||
| const url = urls[0]; |
There was a problem hiding this comment.
Suggestion: The event payload supports multiple URLs, but only the first entry is processed and all others are silently dropped. This loses user-selected files when the plugin emits more than one path; iterate through all payload URLs or explicitly handle batching. [incomplete implementation]
Severity Level: Major ⚠️
- ⚠️ Multi-file opens import only the first project.
- ⚠️ Users receive no feedback about ignored files.Steps of Reproduction ✅
1. A user selects multiple `.storycraft` or `.scst` files in the OS file explorer and
opens them together via the registered StoryCraft file association, causing the Tauri
deep-link plugin to emit a `deep-link://new-url` event with an array payload like
`['storycraft:///C:/p1.storycraft', 'storycraft:///C:/p2.storycraft']`.
2. The deep-link listener registered in `initTauriDeepLink` at
`/workspace/StoryCraft-Studio/services/tauriDeepLink.ts:33-37` executes, and the handler
computes `const urls = Array.isArray(event.payload) ? event.payload : [event.payload];`
and then `const url = urls[0];` at lines 35–36.
3. Only `urls[0]` is normalized to a file path and read via `readTextFile(filePath)` at
lines 52–68, then passed into the existing import flow via
`dispatch(importProjectThunk(file))` at lines 76–78; the remaining `urls[1..]` entries are
ignored and never processed.
4. As a result, StoryCraft opens just the first selected project while silently dropping
additional files from the same OS open operation, without any notification that some
requested projects were ignored.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/tauriDeepLink.ts
**Line:** 35:36
**Comment:**
*Incomplete Implementation: The event payload supports multiple URLs, but only the first entry is processed and all others are silently dropped. This loses user-selected files when the plugin emits more than one path; iterate through all payload URLs or explicitly handle batching.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| if (url.startsWith('storycraft://') || url.startsWith('storycraft:')) { | ||
| // On Windows, the URL might be storycraft:///C:/path/to/file.storycraft | ||
| // On Linux, it might be storycraft:///home/user/file.storycraft | ||
| filePath = url.replace(/^storycraft:\/\/?/, '').replace(/^\/+/, '/'); |
There was a problem hiding this comment.
Suggestion: The URL-to-path conversion keeps a leading slash for Windows drive-letter paths (for example storycraft:///C:/... becomes /C:/...), which makes exists/readTextFile fail with file-not-found on Windows. Normalize Windows paths so drive-letter paths are not prefixed with /. [logic error]
Severity Level: Critical 🚨
- ❌ Windows file associations fail to open StoryCraft projects.
- ⚠️ Users see error notifications instead of project loading.Steps of Reproduction ✅
1. On a Windows installation with file associations configured, the user double-clicks
`C:\projects\my-novel.storycraft`, which Tauri's deep-link plugin emits as a
`deep-link://new-url` event with payload like
`storycraft:///C:/projects/my-novel.storycraft` (commented at
`/workspace/StoryCraft-Studio/services/tauriDeepLink.ts:56`).
2. The handler in `initTauriDeepLink` at
`/workspace/StoryCraft-Studio/services/tauriDeepLink.ts:33-37` receives the event, sets
`let filePath = url;`, and enters the `if (url.startsWith('storycraft://') ||
url.startsWith('storycraft:')) { ... }` block at lines 55–58.
3. The normalization `filePath = url.replace(/^storycraft:\/\/?/, '').replace(/^\/+/,
'/');` removes only `storycraft://` from `storycraft:///C:/...`, leaving
`/C:/projects/my-novel.storycraft`; the second replace collapses leading slashes to a
single `/` instead of stripping them, so `filePath` becomes
`/C:/projects/my-novel.storycraft` rather than `C:/projects/my-novel.storycraft`.
4. The subsequent `exists(filePath)` and `readTextFile(filePath)` calls at lines 62–68 are
issued against `/C:/projects/my-novel.storycraft`, which does not exist on Windows,
causing the code to throw "File not found" and execute the error path that dispatches
`statusActions.addNotification({ title: 'Failed to open project file', ... })` without
ever importing the project.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/tauriDeepLink.ts
**Line:** 55:58
**Comment:**
*Logic Error: The URL-to-path conversion keeps a leading slash for Windows drive-letter paths (for example `storycraft:///C:/...` becomes `/C:/...`), which makes `exists`/`readTextFile` fail with file-not-found on Windows. Normalize Windows paths so drive-letter paths are not prefixed with `/`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
User description
Implements native file associations and single-instance behavior for Tauri desktop builds.
CodeAnt-AI Description
Open StoryCraft project files directly in the app
What Changed
.storycraftand.scstfiles now opens them in StoryCraft StudioImpact
✅ Faster project opening✅ Fewer duplicate app windows✅ Clearer file-open error messages💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.