diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 0000000..4926eec
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "vite",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": ["run", "dev:web"],
+ "port": 5173
+ }
+ ]
+}
diff --git a/package.json b/package.json
index c19282a..ddf1deb 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"scripts": {
"predev": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\kill-stale-instances.ps1 && tsc -p tsconfig.electron.json && copy electron\\preload.cjs dist-electron\\preload.cjs",
"dev": "concurrently -k \"vite --host 127.0.0.1\" \"wait-on tcp:5173 && cross-env VITE_DEV_SERVER_URL=http://127.0.0.1:5173 electron .\"",
+ "dev:web": "vite --host 127.0.0.1",
"dev:admin": ".\\dev-admin.cmd",
"build": "tsc && vite build && tsc -p tsconfig.electron.json && copy electron\\\\preload.cjs dist-electron\\\\preload.cjs",
"preview": "vite preview --host 127.0.0.1",
diff --git a/src/features/layout/AppLayout.tsx b/src/features/layout/AppLayout.tsx
index 170c776..6d33249 100644
--- a/src/features/layout/AppLayout.tsx
+++ b/src/features/layout/AppLayout.tsx
@@ -90,7 +90,7 @@ export function AppTopbar({
Lokaler Windows-Assistent
-
Scanne dein Inventar, triff einfache Artifact-Entscheidungen.
+
Inventar scannen, Artifacts entscheiden.
{topbarStatus && {topbarStatus} }
@@ -107,15 +107,6 @@ export function AppTopbar({
{overlayIcon}
{overlayButtonLabel}
-
- {demoIcon}
- {demoButtonLabel}
-
);
diff --git a/src/features/layout/navigation.ts b/src/features/layout/navigation.ts
index 9d1854e..8880a6d 100644
--- a/src/features/layout/navigation.ts
+++ b/src/features/layout/navigation.ts
@@ -1,4 +1,4 @@
-import { Layers3, Radar, Wand2, Eye } from "lucide-react";
+import { Layers3, Radar, Wand2, Eye, Wrench } from "lucide-react";
import type { AppNavigationItem } from "./types";
export const appNavigationItems: AppNavigationItem[] = [
@@ -6,5 +6,6 @@ export const appNavigationItems: AppNavigationItem[] = [
{ id: "triage", label: "Triage", icon: Layers3 },
{ id: "builds", label: "Builds", icon: Wand2 },
{ id: "overlay", label: "Overlay", icon: Eye },
+ { id: "diagnose", label: "Diagnose", icon: Wrench },
];
diff --git a/src/features/layout/types.ts b/src/features/layout/types.ts
index 74ce470..5b6e564 100644
--- a/src/features/layout/types.ts
+++ b/src/features/layout/types.ts
@@ -1,7 +1,7 @@
import { type ComponentType, type ReactNode } from "react";
import type { LucideProps } from "lucide-react";
-export type NavigationId = "scan" | "triage" | "builds" | "overlay";
+export type NavigationId = "scan" | "triage" | "builds" | "overlay" | "diagnose";
export interface AppNavigationItem {
id: NavigationId;
diff --git a/src/features/scan/ScanView.tsx b/src/features/scan/ScanView.tsx
index 26b391f..54eca1e 100644
--- a/src/features/scan/ScanView.tsx
+++ b/src/features/scan/ScanView.tsx
@@ -1,9 +1,29 @@
-import { ScanViewLayout } from "./components/ScanViewLayout";
+import { ScanViewLayout } from "./components/ScanViewLayout";
+import { DiagnosticsView } from "./components/DiagnosticsView";
import { useScanViewController } from "./hooks/useScanViewController";
import type { ScanViewProps } from "./types";
-export function ScanView(props: ScanViewProps) {
+interface ScanViewExtraProps {
+ /** "workspace" shows the clean scan surface; "diagnose" shows all dev info. */
+ mode?: "workspace" | "diagnose";
+ onDemoScan?: () => void;
+ canDemoScan?: boolean;
+}
+
+export function ScanView({ mode = "workspace", onDemoScan, canDemoScan, ...props }: ScanViewProps & ScanViewExtraProps) {
const controller = useScanViewController(props);
+ if (mode === "diagnose") {
+ return (
+
+ );
+ }
+
return ;
}
diff --git a/src/features/scan/components/DiagnosticsView.tsx b/src/features/scan/components/DiagnosticsView.tsx
new file mode 100644
index 0000000..ebbb992
--- /dev/null
+++ b/src/features/scan/components/DiagnosticsView.tsx
@@ -0,0 +1,203 @@
+import { AlertTriangle, Play, Wrench } from "lucide-react";
+import type { CaptureResult } from "../../../types/global";
+import type { ScanViewControllerResult } from "../types";
+import { FieldConfidenceList } from "./ScanResultCards";
+import { useScanDiagnosticsModalModel } from "./modals/hooks/useScanDiagnosticsModalModel";
+import { useScanDetailsModalModel } from "./modals/hooks/useScanDetailsModalModel";
+
+interface DiagnosticsViewProps {
+ controller: ScanViewControllerResult;
+ latestCapture: CaptureResult | null;
+ captureStatus: string;
+ onDemoScan?: () => void;
+ canDemoScan?: boolean;
+}
+
+// All developer / diagnostic surfaces live here, separated from the Scan
+// workspace: runtime + rights, grid detection, learning + data-package status,
+// fingerprint, auto-scan counters, the automation log, and the raw crop/OCR/
+// confidence dump. Read-only; it drives no scan action except the demo snapshot.
+export function DiagnosticsView({ controller, latestCapture, captureStatus, onDemoScan, canDemoScan }: DiagnosticsViewProps) {
+ const {
+ statusTitle,
+ rightsClassName,
+ rightsValue,
+ genshinClassName,
+ genshinValue,
+ shouldShowAdminBanner,
+ gridSourceClass,
+ gridMainValue,
+ gridMetaValue,
+ learningRulesText,
+ learningRulesSubtext,
+ autoScanModeLabel,
+ fingerprintText,
+ runtimeRows,
+ scanLimitText,
+ autoScanStatsLines,
+ playerProgress,
+ reviewStatus,
+ automationLogLines,
+ canSaveReviewSample,
+ handleSaveReviewSample,
+ } = useScanDiagnosticsModalModel({
+ setDetailsOpen: controller.setDetailsOpen,
+ setDiagnosticsOpen: controller.setDiagnosticsOpen,
+ saveReviewSample: controller.saveReviewSample,
+ canSaveReviewSample: controller.canSaveReviewSample,
+ latestCapture,
+ controller,
+ captureStatus,
+ });
+
+ const { parsedNotes, showParsedNotes, cropRows, ocrRows, debugText, showCrops, showOcr } = useScanDetailsModalModel({
+ setDetailsOpen: controller.setDetailsOpen,
+ parsedArtifact: controller.parsedArtifact,
+ latestCapture,
+ });
+
+ return (
+
+
+
+
Diagnose & Dev
+
Laufzeit, Erkennung & Rohdaten
+
+
+
+
+ {statusTitle}
+
+ {onDemoScan && (
+
+
+ Demo-Daten laden
+
+ )}
+
+
+
+
+
+
Status
+
+
+ App-Rechte
+ {rightsValue}
+
+
+ Genshin
+ {genshinValue}
+
+
+ {shouldShowAdminBanner && (
+
+ App laeuft im Standard-Modus. Auto-Scan braucht Administrator-Rechte: App schliessen und als Administrator neu starten.
+
+ )}
+
+ Tile grid
+ {gridMainValue}
+ {gridMetaValue}
+
+
+ Learning & Daten
+ {learningRulesText}
+ {learningRulesSubtext}
+
+
+ Fingerprint
+ {fingerprintText}
+ Aktiver Capture-Fingerprint fuer die Duplikat-Erkennung.
+
+
+
+
+
{autoScanModeLabel}
+ {playerProgress.show ? (
+
+ {autoScanStatsLines.map((entry) => (
+
+ {entry.value}
+ {entry.label}
+
+ ))}
+
+ ) : (
+
Noch keine Scan-Aktivitaet.
+ )}
+
{scanLimitText}
+
+ {runtimeRows.map((row) => (
+ {row}
+ ))}
+
+ {reviewStatus &&
{reviewStatus}
}
+
+
+
+
+
Automation log
+
+ {automationLogLines.length > 0 ? (
+ automationLogLines.map((line, index) => {line} )
+ ) : (
+ Keine Scan-Aktivitaet.
+ )}
+
+
+
+
+
+
Crops, OCR & Confidence
+
+
+ Review-Sample speichern
+
+
+ {controller.parsedArtifact ? (
+ <>
+
+ {showParsedNotes && (
+
+ {parsedNotes.map((note) => (
+ {note}
+ ))}
+
+ )}
+ >
+ ) : (
+
Noch kein Artifact gelesen. Lies ein Artifact im Scan-Tab, um Crops und OCR zu sehen.
+ )}
+ {showCrops && (
+
+ {cropRows.map((crop) => (
+
+
+
+ {crop.label}
+ {crop.x},{crop.y} - {crop.width}x{crop.height}
+
+
+ ))}
+
+ )}
+ {showOcr && (
+
+
OCR candidates
+ {ocrRows.map((entry) => (
+
+
+ {entry.label}
+ {entry.confidence}% confidence
+
+
{entry.text}
+
+ ))}
+
+ )}
+ {latestCapture &&
{debugText}
}
+
+
+ );
+}
diff --git a/src/features/scan/components/ScanMainSection.tsx b/src/features/scan/components/ScanMainSection.tsx
index 600f9b4..0246328 100644
--- a/src/features/scan/components/ScanMainSection.tsx
+++ b/src/features/scan/components/ScanMainSection.tsx
@@ -1,4 +1,4 @@
-import { AlertTriangle, Camera, Eye } from "lucide-react";
+import { AlertTriangle, Camera } from "lucide-react";
import { ArtifactResultCard } from "./ScanResultCards";
import { useScanMainSectionModel } from "./hooks/useScanMainSectionModel";
import type { ScanMainSectionProps } from "./types";
@@ -63,9 +63,7 @@ export function ScanMainSection({
Quelle {sourceLabel}
-
Grid {gridLabel}
Inventar {inventoryLabel}
-
Modus {captureModeText}
@@ -81,17 +79,8 @@ export function ScanMainSection({
{targetLabel}
{dbLabel}
{reviewLabel}
- {rulesLabel}
-
-
- Details
-
Review Queue
diff --git a/src/features/scan/components/ScanModalsSection.tsx b/src/features/scan/components/ScanModalsSection.tsx
index 71378c3..05eb022 100644
--- a/src/features/scan/components/ScanModalsSection.tsx
+++ b/src/features/scan/components/ScanModalsSection.tsx
@@ -1,46 +1,27 @@
-import { ScanDiagnosticsModal } from "./modals/ScanDiagnosticsModal";
import { ScanSettingsModal } from "./modals/ScanSettingsModal";
-import { ScanDetailsModal } from "./modals/ScanDetailsModal";
import { ScanReviewQueueModal } from "./modals/ScanReviewQueueModal";
import { ScanSummaryModal } from "./modals/ScanSummaryModal";
import type { ScanModalsSectionProps } from "./types";
+// Diagnostics + crop/OCR details moved to the dedicated Diagnose view; only the
+// core workspace modals live here.
export function ScanModalsSection({
- captureStatus,
latestCapture,
- diagnosticsOpen,
settingsOpen,
- detailsOpen,
reviewQueueOpen,
controller,
- setDiagnosticsOpen,
setSettingsOpen,
- setDetailsOpen,
setReviewQueueOpen,
setScanSummary,
}: ScanModalsSectionProps) {
return (
<>
-
-
Scanner
Artifact capture workspace
- Grosse Vorschau vorn, klare Aktionen oben, Diagnose und Review nur bei Bedarf.
+ Quelle waehlen, Auto-Scan starten, Ergebnis rechts pruefen. Dev-Details im Diagnose-Tab.
{bridgeStatusText}
@@ -118,15 +117,6 @@ export function ScanTopControlsSection({
Scan-Setup
-
-
- Scanner Diagnose
-
diff --git a/src/features/scan/components/modals/ScanDetailsModal.tsx b/src/features/scan/components/modals/ScanDetailsModal.tsx
deleted file mode 100644
index 30b2d53..0000000
--- a/src/features/scan/components/modals/ScanDetailsModal.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { FieldConfidenceList } from "../ScanResultCards";
-import type { ScanDetailsModalProps } from "./types";
-import { useScanDetailsModalModel } from "./hooks/useScanDetailsModalModel";
-
-export function ScanDetailsModal({
- open,
- latestCapture,
- controller,
- setDetailsOpen,
-}: ScanDetailsModalProps) {
- const { parsedArtifact } = controller;
- const {
- closeDetails,
- stopPropagation,
- parsedNotes,
- showParsedNotes,
- cropRows,
- ocrRows,
- debugText,
- showCrops,
- showOcr,
- } = useScanDetailsModalModel({
- setDetailsOpen,
- parsedArtifact,
- latestCapture,
- });
-
- if (!open || !latestCapture) return null;
-
- return (
-
-
-
-
-
Dev
-
Crops, OCR & Confidence
-
-
Schliessen
-
-
- {parsedArtifact && (
- <>
-
- {showParsedNotes && (
-
- {parsedNotes.map((note) => {note} )}
-
- )}
- >
- )}
- {showCrops && (
-
- {cropRows.map((crop) => (
-
-
-
- {crop.label}
- {crop.x},{crop.y} - {crop.width}x{crop.height}
-
-
- ))}
-
- )}
- {showOcr && (
-
-
OCR candidates
- {ocrRows.map((entry) => (
-
-
- {entry.label}
- {entry.confidence}% confidence
-
-
{entry.text}
-
- ))}
-
- )}
-
{debugText}
-
-
-
- );
-}
diff --git a/src/features/scan/components/modals/ScanDiagnosticsModal.tsx b/src/features/scan/components/modals/ScanDiagnosticsModal.tsx
deleted file mode 100644
index 45bfe6b..0000000
--- a/src/features/scan/components/modals/ScanDiagnosticsModal.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { AlertTriangle, Eye, Wrench } from "lucide-react";
-import type { ScanDiagnosticsModalProps } from "./types";
-import { useScanDiagnosticsModalModel } from "./hooks/useScanDiagnosticsModalModel";
-
-export function ScanDiagnosticsModal({
- open,
- captureStatus,
- latestCapture,
- controller,
- setDetailsOpen,
- setDiagnosticsOpen,
-}: ScanDiagnosticsModalProps) {
- const {
- toggleDevMode,
- } = controller;
-
- const {
- closeDiagnostics,
- openDetails,
- handleSaveReviewSample,
- stopPropagation,
- canOpenDetails,
- statusTitle,
- rightsClassName,
- rightsValue,
- genshinClassName,
- genshinValue,
- shouldShowAdminBanner,
- gridSourceClass,
- gridMainValue,
- gridMetaValue,
- learningRulesText,
- learningRulesSubtext,
- autoScanModeLabel,
- autoScanRunning,
- fingerprintText,
- runtimeRows,
- scanLimitText,
- scanTipText,
- autoScanStatsLines,
- playerProgress,
- showDevRows,
- reviewStatus,
- automationLogLines,
- canSaveReviewSample,
- } = useScanDiagnosticsModalModel({
- setDetailsOpen,
- setDiagnosticsOpen,
- saveReviewSample: controller.saveReviewSample,
- canSaveReviewSample: controller.canSaveReviewSample,
- latestCapture,
- controller,
- captureStatus,
- });
-
- if (!open) return null;
-
- return (
-
-
-
-
-
Scanner Diagnose
-
Input, Grid & Lernstatus
-
-
Schliessen
-
-
-
-
-
- {statusTitle}
-
-
-
-
-
- App-Rechte
- {rightsValue}
-
-
- Genshin
- {genshinValue}
-
-
- {shouldShowAdminBanner && (
-
- App laeuft im Standard-Modus. Auto-Scan braucht Administrator-Rechte: App schliessen und als Administrator neu starten (z.B. Terminal per Rechtsklick "Als Administrator ausfuehren" und darin "npm run dev").
-
- )}
-
-
- Tile grid
- {gridMainValue}
- {gridMetaValue}
-
-
-
- Learning
- {learningRulesText}
- {learningRulesSubtext}
-
-
- {playerProgress.show && (
-
- {autoScanModeLabel}
- {autoScanStatsLines.map((entry) => (
-
- {entry.value}
- {entry.label}
-
- ))}
-
- )}
-
-
- Fingerprint
- {fingerprintText}
- Active capture fingerprint used for deterministic duplicate guard checks.
-
-
- {showDevRows && (
-
- {runtimeRows.map((row) => (
- {row}
- ))}
-
- )}
-
- {autoScanRunning && playerProgress.show && (
-
- )}
-
- {reviewStatus && (
-
{reviewStatus}
- )}
-
-
-
Automation
-
- {automationLogLines.length > 0 ? (
- automationLogLines.map((line, index) => (
- {line}
- ))
- ) : (
- No scan activity yet.
- )}
-
-
-
-
-
-
- Crops, OCR & Confidence
-
-
-
- Review-Sample speichern
-
-
-
-
{scanLimitText}
-
{scanTipText}
-
-
-
- );
-}
diff --git a/src/pages/app/AppPageLayout.tsx b/src/pages/app/AppPageLayout.tsx
index e9648bd..483add4 100644
--- a/src/pages/app/AppPageLayout.tsx
+++ b/src/pages/app/AppPageLayout.tsx
@@ -53,9 +53,12 @@ export function AppPageLayout({ controller }: AppPageLayoutProps) {
overlayIcon={
}
demoIcon={
}
/>
-
- {activeView === "scan" && (
+ {activeView !== "diagnose" &&
}
+ {(activeView === "scan" || activeView === "diagnose") && (