Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
"editor.defaultFormatter": "vscode.json-language-features"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
Expand Down
12 changes: 12 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
hasPassphraseSentinel,
isIdbEncryptionReady,
} from './services/storage/storageEncryptionService';
import { initTauriDeepLink } from './services/tauriDeepLink';
import { registerTauriMenuHandler, unregisterTauriMenuHandler } from './services/tauriMenuService';
import { viewNavigationLabelKey } from './services/viewNavigationLabels';
import type { View } from './types';
Expand Down Expand Up @@ -490,6 +491,17 @@ const App: FC<AppProps> = ({ isNewUser }) => {
return () => unregisterTauriMenuHandler();
}, [executeCommand]);

// QNBS-v3: Tauri deep link handler for native file associations (.storycraft, .scst)
useEffect(() => {
let cleanup: (() => void) | undefined;
void initTauriDeepLink(dispatch, t).then((fn) => {
cleanup = fn;
});
return () => {
cleanup?.();
};
}, [dispatch, t]);

const renderView = () => {
switch (currentView) {
case 'dashboard':
Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Status: 🔄 in progress | ⬜ open | ✅ done
- ✅ **P0-2** — Coverage C-7: 96 neue Tests geschrieben (Ziel 90 übertroffen).
- LoRA: datasetBuilder (19) + evaluationService (16)
- Voice: intentEngine (17) + feedbackService (23) + audioNavigator (21)
- ✅ **P0-4** — Native File Associations + Single-Instance: `.storycraft`/`.scst` extensions registered, deep link handler in `services/tauriDeepLink.ts`, Rust `RunEvent::Opened`/`RunEvent::SecondInstance` handlers in `lib.rs`.
- 🔄 **P0-3** — Quality Gates stabil: lint ✅ · typecheck ✅ · i18n:check ✅ (2026-06-07). parity:check + bundle:budget + smoke:prod benötigen Build → CI-only auf Low-End. Warte auf nächsten CI-Run für Bestätigung.

### P1 — AI Resilience & Core Reliability
Expand Down
30 changes: 30 additions & 0 deletions docs/TAURI-CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ Complete these steps once before pushing the first signed release tag:
| Updater | `docs/TAURI-UPDATER.md` when publishing signed tags |
| Local CI | Heavy builds optional — `infra/low-end-ci/` act + native `pnpm run ci:quick` |
| Version UI | Settings shows app version; matches release tag after install |
| File Associations | `.storycraft` and `.scst` extensions registered; double-click opens project |
| Single-Instance | Second instance focuses main window + opens file via `RunEvent::SecondInstance` |

## Native File Associations (v1.20)

StoryCraft Studio registers `.storycraft` and `.scst` file extensions for native project opening.

### Configuration

- `tauri.conf.json` → `bundle.fileAssociations` registers both extensions with `role: "Editor"`
- `tauri.conf.json` → `app.deepLink.protocols` enables `storycraft://` protocol
- `src-tauri/src/lib.rs` → `RunEvent::Opened` and `RunEvent::SecondInstance` handlers
- `services/tauriDeepLink.ts` → Frontend event listener for `open-project-file`

### Behavior

1. **Double-click** a `.storycraft` or `.scst` file → App opens (or activates existing instance) and loads the project
2. **Drag-drop** file onto app icon → Same behavior as double-click
3. **Second instance** → Main window focused, file opened in existing instance
4. **Error handling** → Toast notification with error details if file cannot be loaded

### Icon Preparation

Create icons in `icons/` directory:
- `icons/storycraft-16x16.png` - File icon for Windows/Linux
- `icons/storycraft-32x32.png` - File icon for Windows/Linux
- `icons/storycraft-48x48.png` - File icon for Windows/Linux
- `icons/storycraft-256x256.png` - File icon for Windows/Linux

macOS uses the existing `icons/icon.icns` for file associations.

**Post-release (updater):**

Expand Down
2 changes: 2 additions & 0 deletions locales/ar/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@
"error.db.invalidState": "خطأ داخلي في قاعدة البيانات. يرجى إعادة تحميل الصفحة.",
"error.db.quotaExceeded": "تخزين المتصفّح ممتلئ. يرجى حذف المشاريع أو اللقطات القديمة.",
"error.db.unknown": "خطأ غير معروف أثناء الوصول إلى قاعدة البيانات.",
"error.deepLink.title": "Failed to open project file",
"error.deepLink.unknown": "Unknown error",
"error.fileImportError": "خطأ في استيراد الملف",
"error.mic.denied": "تم رفض الوصول إلى الميكروفون. يرجى منح الإذن في إعدادات متصفّحك.",
"error.mic.generic": "خطأ في التعرّف على الكلام: {{error}}",
Expand Down
2 changes: 1 addition & 1 deletion locales/ar/portal.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"portal.back": "رجوع",
"portal.language.searchPlaceholder": "البحث في اللغات...",
"portal.demo.chapterContent": "تشبّث ضباب الصباح بالأرصفة بينما فردت ليرا خريطة المدينة القديمة. كانت دائرة باهتة تحدّد الركن الشمالي الغربي — البرج الذي لا يذكره أحد.\n\nطوت الورقة وتبعت الممر الضيق حتى ارتفعت أمامها الشبكة الحديدية. كان القفل دافئًا تحت الشمس؛ أما المعدن فظلّ باردًا.",
"portal.demo.chapterTitle": "الفصل الأول — أمام البوابة",
"portal.demo.logline": "عيّنة قصيرة لتجربة المخطط والمخطوطة ومساحة العمل.",
Expand All @@ -18,6 +17,7 @@
"portal.features.worldbuilding.description": "Rich dossiers, atlases, AI portraits, and relationship maps.",
"portal.features.worldbuilding.title": "Characters & Worlds",
"portal.language.groupLabel": "لغة شاشة الترحيب",
"portal.language.searchPlaceholder": "البحث في اللغات...",
"portal.new.ai.description": "قدّم فكرة بسيطة ودع الذكاء الاصطناعي يبني لك مخطط حبكة مفصّلًا.",
"portal.new.ai.title": "التوليد بالذكاء الاصطناعي",
"portal.new.blank.description": "لمن يعرف وجهته. ابدأ بفصل أول فارغ.",
Expand Down
2 changes: 2 additions & 0 deletions locales/de/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@
"error.ollama.unreachable": "Ollama nicht erreichbar ({{url}}): {{message}}",
"error.ollama.unreachableHint": "Ollama nicht erreichbar ({{url}}). Stellen Sie sicher, dass Ollama läuft: ollama serve",
"error.snapshotError": "Sicherungsfehler",
"error.deepLink.title": "Fehler beim Öffnen der Projektdatei",
"error.deepLink.unknown": "Unbekannter Fehler",
"export.epubExport": "EPUB 3.0 exportieren",
"export.section": "Abschnitt {{index}}",
"header.openMenu": "Menü öffnen",
Expand Down
56 changes: 28 additions & 28 deletions locales/el/characterInterviews.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
{
"characterInterviews.title": "Character Interviews",
"characterInterviews.subtitle": "Interview your characters in character voice",
"characterInterviews.emptyState": "No interviews yet. Select a character to begin.",
"characterInterviews.selectCharacter": "Select Character",
"characterInterviews.selectCharacterPlaceholder": "Choose a character…",
"characterInterviews.selectArchetype": "Select Archetype",
"characterInterviews.archetypeLabel": "Archetype",
"characterInterviews.archetypeAlly": "Ally",
"characterInterviews.archetypeDescription": "The archetype determines the interview template and character voice.",
"characterInterviews.startInterview": "Start Interview",
"characterInterviews.newInterview": "New Interview",
"characterInterviews.deleteInterview": "Delete Interview",
"characterInterviews.deleteInterviewConfirm": "Delete this interview? This cannot be undone.",
"characterInterviews.interviewTitle": "Interview Title",
"characterInterviews.interviewTitlePlaceholder": "Optional title…",
"characterInterviews.noCharacters": "No characters found. Add characters in the Characters view first.",
"characterInterviews.sendQuestion": "Send",
"characterInterviews.questionPlaceholder": "Ask a question…",
"characterInterviews.suggestedQuestions": "Suggested Questions",
"characterInterviews.customQuestion": "Custom Question",
"characterInterviews.thinking": "Thinking…",
"characterInterviews.stopGeneration": "Stop",
"characterInterviews.archetypeHerald": "Herald",
"characterInterviews.archetypeHero": "Hero",
"characterInterviews.archetypeLabel": "Archetype",
"characterInterviews.archetypeMentor": "Mentor",
"characterInterviews.archetypeVillain": "Villain",
"characterInterviews.archetypeShadow": "Shadow",
"characterInterviews.archetypeTrickster": "Trickster",
"characterInterviews.archetypeShapeshifter": "Shapeshifter",
"characterInterviews.archetypeHerald": "Herald",
"characterInterviews.archetypeAlly": "Ally",
"characterInterviews.archetypeThresholdGuardian": "Threshold Guardian",
"characterInterviews.messageRoleUser": "Interviewer",
"characterInterviews.messageRoleAi": "Character",
"characterInterviews.archetypeTrickster": "Trickster",
"characterInterviews.archetypeVillain": "Villain",
"characterInterviews.cancel": "Cancel",
"characterInterviews.copyResponse": "Copy response",
"characterInterviews.noAiKey": "No AI key configured. Add a Gemini API key in Settings to use interviews.",
"characterInterviews.customQuestion": "Custom Question",
"characterInterviews.deleteInterview": "Delete Interview",
"characterInterviews.deleteInterviewConfirm": "Delete this interview? This cannot be undone.",
"characterInterviews.emptyState": "No interviews yet. Select a character to begin.",
"characterInterviews.interviewTitle": "Interview Title",
"characterInterviews.interviewTitlePlaceholder": "Optional title…",
"characterInterviews.interviewsForCharacter": "Interviews for {{name}}",
"characterInterviews.messageRoleAi": "Character",
"characterInterviews.messageRoleUser": "Interviewer",
"characterInterviews.newInterview": "New Interview",
"characterInterviews.noAiKey": "No AI key configured. Add a Gemini API key in Settings to use interviews.",
"characterInterviews.noCharacters": "No characters found. Add characters in the Characters view first.",
"characterInterviews.questionPlaceholder": "Ask a question…",
"characterInterviews.save": "Save",
"characterInterviews.cancel": "Cancel"
"characterInterviews.selectArchetype": "Select Archetype",
"characterInterviews.selectCharacter": "Select Character",
"characterInterviews.selectCharacterPlaceholder": "Choose a character…",
"characterInterviews.sendQuestion": "Send",
"characterInterviews.startInterview": "Start Interview",
"characterInterviews.stopGeneration": "Stop",
"characterInterviews.subtitle": "Interview your characters in character voice",
"characterInterviews.suggestedQuestions": "Suggested Questions",
"characterInterviews.thinking": "Thinking…",
"characterInterviews.title": "Character Interviews"
}
104 changes: 52 additions & 52 deletions locales/el/characters.json
Original file line number Diff line number Diff line change
@@ -1,73 +1,73 @@
{
"characters.title": "Character Dossiers",
"characters.description": "Build and manage your cast. Click a card to open their full dossier.",
"characters.addNewManually": "Add Manually",
"characters.addNewManuallyHint": "Create a new character from scratch.",
"characters.addNewWithAI": "Generate with AI",
"characters.addNewWithAIHint": "Describe a concept to generate a profile.",
"characters.newCharacterName": "New Character",
"characters.yourCharacters": "Your Characters",
"characters.noCharacters": "No characters created yet.",
"characters.edit.name": "Name",
"characters.edit.backstory": "Backstory",
"characters.edit.motivation": "Motivation",
"characters.edit.appearance": "Appearance",
"characters.edit.notes": "Notes",
"characters.edit.arc": "Character Arc",
"characters.edit.relationships": "Relationships",
"characters.selectCharacter": "Select a character to edit or add a new one.",
"characters.tabs.profile": "Profile",
"characters.tabs.arc": "Character Arc",
"characterGraph.title": "Character Relationship Graph",
"characterGraph.relationships": "Relationships",
"characterGraph.legend": "Legend",
"characterGraph.view.graph": "Graph view",
"characterGraph.view.table": "Table view",
"characterGraph.view.graphKeyboardHint": "Interactive canvas — not keyboard-navigable. Press G to switch to the accessible table view.",
"characterGraph.graphAriaLabel": "Interactive relationship graph between characters",
"characterGraph.legend": "Legend",
"characterGraph.relationships": "Relationships",
"characterGraph.table.caption": "Relationships as an accessible table",
"characterGraph.table.empty": "No relationships yet.",
"characterGraph.table.emptyHint": "Add relationships between characters in their detail panels to see them here.",
"characterGraph.table.from": "From",
"characterGraph.table.strength": "Strength",
"characterGraph.table.to": "To",
"characterGraph.table.type": "Type",
"characterGraph.table.strength": "Strength",
"characterGraph.table.empty": "No relationships yet.",
"characterGraph.table.emptyHint": "Add relationships between characters in their detail panels to see them here.",
"characters.tabs.relationships": "Relationships",
"characters.tabs.notes": "Notes",
"characters.dossier.title": "Dossier: {{name}}",
"characterGraph.title": "Character Relationship Graph",
"characterGraph.view.graph": "Graph view",
"characterGraph.view.graphKeyboardHint": "Interactive canvas — not keyboard-navigable. Press G to switch to the accessible table view.",
"characterGraph.view.table": "Table view",
"characters.addNewManually": "Add Manually",
"characters.addNewManuallyHint": "Create a new character from scratch.",
"characters.addNewWithAI": "Generate with AI",
"characters.addNewWithAIHint": "Describe a concept to generate a profile.",
"characters.aiModal.button": "Generate Profile",
"characters.aiModal.description": "Provide a core concept and AI will create a detailed profile, including backstory, motivation, appearance, and more.",
"characters.aiModal.placeholder": "e.g., 'A grizzled space pirate with a secret heart of gold'",
"characters.aiModal.title": "Generate Character with AI",
"characters.deleteConfirm": "Are you sure you want to permanently delete this character? This action cannot be undone.",
"characters.deleteLabel": "Delete {{name}}",
"characters.description": "Build and manage your cast. Click a card to open their full dossier.",
"characters.dossier.refineButton": "Refine with AI",
"characters.dossier.refineLabel": "Refine Portrait",
"characters.dossier.refinePlaceholder": "e.g., 'Add a scar', 'make hair red'",
"characters.dossier.refineButton": "Refine with AI",
"characters.edit.personality": "Personality Traits",
"characters.dossier.title": "Dossier: {{name}}",
"characters.edit.appearance": "Appearance",
"characters.edit.arc": "Character Arc",
"characters.edit.backstory": "Backstory",
"characters.edit.flaws": "Flaws",
"characters.edit.generateFullProfile": "Generate Full Profile with AI",
"characters.edit.regenerate": "Regenerate",
"characters.edit.portrait.title": "Character Portrait",
"characters.edit.motivation": "Motivation",
"characters.edit.name": "Name",
"characters.edit.notes": "Notes",
"characters.edit.personality": "Personality Traits",
"characters.edit.portrait.generateButton": "Generate / Regenerate",
"characters.edit.portrait.generating": "Generating...",
"characters.aiModal.title": "Generate Character with AI",
"characters.aiModal.description": "Provide a core concept and AI will create a detailed profile, including backstory, motivation, appearance, and more.",
"characters.aiModal.placeholder": "e.g., 'A grizzled space pirate with a secret heart of gold'",
"characters.aiModal.button": "Generate Profile",
"characters.loading.profile": "Generating detailed profile...",
"characters.error.portraitFailed": "Portrait generation failed. Try adding more detail to the appearance description.",
"characters.edit.portrait.styleLabel": "Art Style",
"characters.edit.portrait.styles.digitalPainting": "Digital Painting",
"characters.edit.portrait.styles.photorealistic": "Photorealistic",
"characters.edit.portrait.styles.anime": "Anime",
"characters.edit.portrait.styles.cartoon": "Cartoon",
"characters.edit.portrait.styles.watercolor": "Watercolor",
"characters.edit.portrait.styles.comicBook": "Comic Book",
"characters.edit.portrait.styles.digitalPainting": "Digital Painting",
"characters.edit.portrait.styles.oilPainting": "Oil Painting",
"characters.edit.portrait.styles.photorealistic": "Photorealistic",
"characters.edit.portrait.styles.sketch": "Sketch",
"characters.edit.portrait.styles.comicBook": "Comic Book",
"characters.deleteLabel": "Delete {{name}}",
"characters.deleteConfirm": "Are you sure you want to permanently delete this character? This action cannot be undone.",
"characters.uploadImage": "Upload Image",
"characters.edit.portrait.styles.watercolor": "Watercolor",
"characters.edit.portrait.title": "Character Portrait",
"characters.edit.regenerate": "Regenerate",
"characters.edit.relationships": "Relationships",
"characters.editorTabsAriaLabel": "Character editor tabs",
"characters.stats.total": "Total",
"characters.error.portraitFailed": "Portrait generation failed. Try adding more detail to the appearance description.",
"characters.loading.profile": "Generating detailed profile...",
"characters.newCharacterName": "New Character",
"characters.noCharacters": "No characters created yet.",
"characters.searchAriaLabel": "Search characters by name",
"characters.searchPlaceholder": "Search characters…",
"characters.selectCharacter": "Select a character to edit or add a new one.",
"characters.stats.avgCompleteness": "Avg. complete",
"characters.stats.developed": "Developed",
"characters.stats.total": "Total",
"characters.stats.withPortrait": "With portrait",
"characters.stats.avgCompleteness": "Avg. complete",
"characters.searchPlaceholder": "Search characters…",
"characters.searchAriaLabel": "Search characters by name"
"characters.tabs.arc": "Character Arc",
"characters.tabs.notes": "Notes",
"characters.tabs.profile": "Profile",
"characters.tabs.relationships": "Relationships",
"characters.title": "Character Dossiers",
"characters.uploadImage": "Upload Image",
"characters.yourCharacters": "Your Characters"
}
2 changes: 2 additions & 0 deletions locales/el/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@
"error.db.invalidState": "Σφάλμα εσωτερικής βάσης δεδομένων. Επαναλάβετε τη φόρτωση της σελίδας.",
"error.db.quotaExceeded": "Ο χώρος αποθήκευσης του προγράμματος περιήγησης είναι πλήρης. Διαγράψτε παλιά έργα ή στιγμιότυπα.",
"error.db.unknown": "Άγνωστο σφάλμα κατά την πρόσβαση στη βάση δεδομένων.",
"error.deepLink.title": "Failed to open project file",
"error.deepLink.unknown": "Unknown error",
"error.fileImportError": "Σφάλμα εισαγωγής αρχείου",
"error.mic.denied": "Δεν επιτρέπεται η πρόσβαση στο μικρόφωνο. Παρακαλούμε χορηγήστε άδεια στις ρυθμίσεις του προγράμματος περιήγησής σας.",
"error.mic.generic": "Σφάλμα αναγνώρισης ομιλίας: {{σφάλμα}}",
Expand Down
Loading
Loading