Skip to content

feat: Native File Associations (.storycraft/.scst) and Single-Instance behavior#87

Merged
qnbs merged 4 commits into
mainfrom
feat/native-file-associations-2026-06-08
Jun 8, 2026
Merged

feat: Native File Associations (.storycraft/.scst) and Single-Instance behavior#87
qnbs merged 4 commits into
mainfrom
feat/native-file-associations-2026-06-08

Conversation

@qnbs

@qnbs qnbs commented Jun 8, 2026

Copy link
Copy Markdown
Owner

User description

Implements native file associations and single-instance behavior for Tauri desktop builds.

  • Adds tauri-plugin-deep-link for custom URL scheme registration
  • Adds tauri-plugin-single-instance with deep-link feature for file association handling
  • Updates tauri.conf.json with fileAssociations and deep-link configuration
  • Fixes Emitter trait import for menu-action event emission

CodeAnt-AI Description

Open StoryCraft project files directly in the app

What Changed

  • Double-clicking .storycraft and .scst files now opens them in StoryCraft Studio
  • If the app is already open, a second launch brings the existing window forward and loads the selected file
  • File-open failures now show a clear error notification instead of failing silently
  • Added project-file handling for the app startup flow and covered it with unit tests

Impact

✅ 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:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

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:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

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.

qnbs added 2 commits June 8, 2026 08:49
- 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

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

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 ·
Reddit ·
LinkedIn

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
storycraft-studio Ready Ready Preview, Comment Jun 8, 2026 8:13am

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 8, 2026
Comment thread services/tauriDeepLink.ts Outdated
dispatch(
statusActions.addNotification({
type: 'error',
title: 'Failed to open project file',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
👍 | 👎

Comment thread services/tauriDeepLink.ts Outdated
statusActions.addNotification({
type: 'error',
title: 'Failed to open project file',
description: resultAction.error?.message ?? 'Unknown error',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
👍 | 👎

Comment thread services/tauriDeepLink.ts Outdated
dispatch(
statusActions.addNotification({
type: 'error',
title: 'Failed to open project file',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
👍 | 👎

Comment thread App.tsx Outdated
Comment on lines +495 to +497
useEffect(() => {
void initTauriDeepLink(dispatch);
}, [dispatch]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
👍 | 👎

Comment thread services/tauriDeepLink.ts Outdated
Comment on lines +35 to +36
const urls = Array.isArray(event.payload) ? event.payload : [event.payload];
const url = urls[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
👍 | 👎

Comment thread services/tauriDeepLink.ts Outdated
Comment on lines +55 to +58
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(/^\/+/, '/');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@qnbs
qnbs enabled auto-merge (squash) June 8, 2026 08:34
@qnbs
qnbs merged commit 1b5fe28 into main Jun 8, 2026
16 checks passed
@qnbs
qnbs deleted the feat/native-file-associations-2026-06-08 branch June 8, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant