chore: initialize repository baseline
Import the existing Electron + React + TypeScript app as the version-control baseline before the scanner rework (C# input/capture sidecar, resolution-anchored layout profiles, OCR preprocessing, eval harness, rescan-merge, GOOD interop). Housekeeping in this commit: - Remove orphaned temp_inputhelper_block.ts (duplicate of the input-helper script). - Ignore .claude/scheduled_tasks.lock local session state. - Add .gitattributes to normalize line endings (LF in repo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import type {
|
||||
AppMetricsProps,
|
||||
AppShellProps,
|
||||
AppSidebarProps,
|
||||
AppTopbarProps,
|
||||
} from "./types";
|
||||
import { useAppSidebarModel } from "./hooks/useAppSidebarModel";
|
||||
import { useAppTopbarModel } from "./hooks/useAppTopbarModel";
|
||||
|
||||
export function AppSidebar({ activeView, items, onSelect }: AppSidebarProps) {
|
||||
const { navigationItems } = useAppSidebarModel({ items, onSelect });
|
||||
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<div className="brand">
|
||||
<div className="brand-mark">GA</div>
|
||||
<div>
|
||||
<div className="brand-title">Artifact Assistant</div>
|
||||
<div className="brand-subtitle">Scanner-first MVP</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="nav-list">
|
||||
{navigationItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
className={`nav-item ${activeView === item.id ? "active" : ""}`}
|
||||
disabled={Boolean(item.disabled)}
|
||||
onClick={item.onSelect}
|
||||
title={item.disabled ? item.disabledReason : undefined}
|
||||
>
|
||||
<Icon size={18} />
|
||||
{item.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="safety-card">
|
||||
<ShieldCheck size={18} />
|
||||
<div>
|
||||
<strong>Sicherheits-Grenzen</strong>
|
||||
<span>Keine Eingriffe am Spielinventar, keine Ressourcen-Aktionen, nur Analyse und Scan.</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppTopbar({
|
||||
topbarStatus,
|
||||
canExportGood,
|
||||
canShowOverlay,
|
||||
isScanning,
|
||||
artifactCount,
|
||||
onShowOverlay,
|
||||
onDemoScan,
|
||||
onExportGood,
|
||||
exportIcon,
|
||||
overlayIcon,
|
||||
demoIcon,
|
||||
}: AppTopbarProps) {
|
||||
const {
|
||||
handleShowOverlay,
|
||||
handleExportGood,
|
||||
handleDemoScan,
|
||||
isExportDisabled,
|
||||
isDemoDisabled,
|
||||
isOverlayDisabled,
|
||||
exportButtonLabel,
|
||||
exportButtonTitle,
|
||||
overlayButtonLabel,
|
||||
overlayButtonTitle,
|
||||
demoButtonLabel,
|
||||
demoButtonTitle,
|
||||
} = useAppTopbarModel({
|
||||
onExportGood,
|
||||
onShowOverlay,
|
||||
onDemoScan,
|
||||
canExportGood,
|
||||
canShowOverlay,
|
||||
isScanning,
|
||||
artifactCount,
|
||||
});
|
||||
|
||||
return (
|
||||
<header className="topbar">
|
||||
<div>
|
||||
<p className="eyebrow">Lokaler Windows-Assistent</p>
|
||||
<h1>Scanne dein Inventar, triff einfache Artifact-Entscheidungen.</h1>
|
||||
</div>
|
||||
<div className="topbar-actions">
|
||||
{topbarStatus && <span className="topbar-status">{topbarStatus}</span>}
|
||||
<button className="ghost-button" onClick={handleExportGood} disabled={isExportDisabled} title={exportButtonTitle}>
|
||||
{exportIcon}
|
||||
<span>{exportButtonLabel}</span>
|
||||
</button>
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={handleShowOverlay}
|
||||
disabled={isOverlayDisabled}
|
||||
title={overlayButtonTitle}
|
||||
>
|
||||
{overlayIcon}
|
||||
<span>{overlayButtonLabel}</span>
|
||||
</button>
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={handleDemoScan}
|
||||
disabled={isDemoDisabled}
|
||||
title={demoButtonTitle}
|
||||
>
|
||||
{demoIcon}
|
||||
{demoButtonLabel}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppMetrics({ metricCards }: AppMetricsProps) {
|
||||
return (
|
||||
<section className="metrics-grid">
|
||||
{metricCards.map((metric) => (
|
||||
<div className="metric" key={metric.label}>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{metric.value}</strong>
|
||||
<small>{metric.detail}</small>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppShell({ sidebar, children }: AppShellProps) {
|
||||
return (
|
||||
<div className="app-shell">
|
||||
{sidebar}
|
||||
<main className="main-panel">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useMemo } from "react";
|
||||
import type { NavigationId } from "../types";
|
||||
import type { AppNavigationItem, AppSidebarItemAction } from "../types";
|
||||
|
||||
export interface AppSidebarModel {
|
||||
navigationItems: AppSidebarItemAction[];
|
||||
}
|
||||
|
||||
interface UseAppSidebarModelInput {
|
||||
items: AppNavigationItem[];
|
||||
onSelect: (id: NavigationId) => void;
|
||||
}
|
||||
|
||||
export function useAppSidebarModel({ items, onSelect }: UseAppSidebarModelInput): AppSidebarModel {
|
||||
const navigationItems = useMemo(
|
||||
() =>
|
||||
items.map((item) => ({
|
||||
...item,
|
||||
onSelect: () => onSelect(item.id),
|
||||
})),
|
||||
[items, onSelect],
|
||||
);
|
||||
|
||||
return {
|
||||
navigationItems,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
export interface AppTopbarModel {
|
||||
handleShowOverlay: () => void;
|
||||
handleExportGood: () => void;
|
||||
handleDemoScan: () => void;
|
||||
isExportDisabled: boolean;
|
||||
isDemoDisabled: boolean;
|
||||
isOverlayDisabled: boolean;
|
||||
exportButtonLabel: string;
|
||||
exportButtonTitle: string;
|
||||
overlayButtonLabel: string;
|
||||
overlayButtonTitle: string;
|
||||
demoButtonLabel: string;
|
||||
demoButtonTitle: string;
|
||||
}
|
||||
|
||||
interface UseAppTopbarModelInput {
|
||||
onExportGood: () => Promise<void> | void;
|
||||
onShowOverlay: () => Promise<void>;
|
||||
onDemoScan: () => void;
|
||||
canExportGood: boolean;
|
||||
canShowOverlay: boolean;
|
||||
isScanning: boolean;
|
||||
artifactCount: number;
|
||||
}
|
||||
|
||||
export function useAppTopbarModel({
|
||||
onExportGood,
|
||||
onShowOverlay,
|
||||
onDemoScan,
|
||||
canExportGood,
|
||||
canShowOverlay,
|
||||
isScanning,
|
||||
artifactCount,
|
||||
}: UseAppTopbarModelInput): AppTopbarModel {
|
||||
const handleShowOverlay = useCallback(() => {
|
||||
void onShowOverlay();
|
||||
}, [onShowOverlay]);
|
||||
|
||||
const handleExportGood = useCallback(() => {
|
||||
void onExportGood();
|
||||
}, [onExportGood]);
|
||||
|
||||
const handleDemoScan = useCallback(() => {
|
||||
onDemoScan();
|
||||
}, [onDemoScan]);
|
||||
|
||||
const isExportDisabled = !canExportGood || artifactCount === 0;
|
||||
const isDemoDisabled = isScanning;
|
||||
const isOverlayDisabled = !canShowOverlay;
|
||||
|
||||
return {
|
||||
handleShowOverlay,
|
||||
handleExportGood,
|
||||
handleDemoScan,
|
||||
isExportDisabled,
|
||||
isDemoDisabled,
|
||||
isOverlayDisabled,
|
||||
exportButtonLabel: "GOOD Export",
|
||||
exportButtonTitle: canExportGood ? "GOOD-Export starten." : "GOOD-Export ist nur in der Electron-App verfuegbar.",
|
||||
overlayButtonLabel: "Overlay",
|
||||
overlayButtonTitle: canShowOverlay ? "Overlay oeffnen." : "Overlay benoetigt die Electron-Bridge.",
|
||||
demoButtonLabel: isScanning ? "Laedt..." : "Demo-Daten",
|
||||
demoButtonTitle: "Laedt Beispieldaten fuer die Triage- und Build-Ansicht.",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { AppShell, AppMetrics, AppSidebar, AppTopbar } from "./AppLayout";
|
||||
export {
|
||||
type AppMetricCard,
|
||||
type AppNavigationItem,
|
||||
type AppBarAction,
|
||||
type NavigationId,
|
||||
type AppSidebarProps,
|
||||
type AppTopbarProps,
|
||||
type AppMetricsProps,
|
||||
type AppShellProps,
|
||||
} from "./types";
|
||||
export { appNavigationItems } from "./navigation";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layers3, Radar, Wand2, Eye } from "lucide-react";
|
||||
import type { AppNavigationItem } from "./types";
|
||||
|
||||
export const appNavigationItems: AppNavigationItem[] = [
|
||||
{ id: "scan", label: "Scan", icon: Radar },
|
||||
{ id: "triage", label: "Triage", icon: Layers3 },
|
||||
{ id: "builds", label: "Builds", icon: Wand2 },
|
||||
{ id: "overlay", label: "Overlay", icon: Eye },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { type ComponentType, type ReactNode } from "react";
|
||||
import type { LucideProps } from "lucide-react";
|
||||
|
||||
export type NavigationId = "scan" | "triage" | "builds" | "overlay";
|
||||
|
||||
export interface AppNavigationItem {
|
||||
id: NavigationId;
|
||||
label: string;
|
||||
icon: ComponentType<LucideProps>;
|
||||
disabled?: boolean;
|
||||
disabledReason?: string;
|
||||
}
|
||||
|
||||
export interface AppSidebarItemAction extends AppNavigationItem {
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export interface AppMetricCard {
|
||||
label: string;
|
||||
value: number;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export interface AppBarAction {
|
||||
id: string;
|
||||
icon?: ReactNode;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
onAction: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface AppSidebarProps {
|
||||
activeView: NavigationId;
|
||||
items: AppNavigationItem[];
|
||||
onSelect: (id: NavigationId) => void;
|
||||
}
|
||||
|
||||
export interface AppTopbarProps {
|
||||
topbarStatus: string;
|
||||
canExportGood: boolean;
|
||||
canShowOverlay: boolean;
|
||||
isScanning: boolean;
|
||||
artifactCount: number;
|
||||
onExportGood: () => Promise<void> | void;
|
||||
onShowOverlay: () => Promise<void>;
|
||||
onDemoScan: () => void;
|
||||
exportIcon?: ReactNode;
|
||||
overlayIcon?: ReactNode;
|
||||
demoIcon?: ReactNode;
|
||||
}
|
||||
|
||||
export interface AppMetricsProps {
|
||||
metricCards: AppMetricCard[];
|
||||
}
|
||||
|
||||
export interface AppShellProps {
|
||||
sidebar: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
Reference in New Issue
Block a user