Spezifikation v0.2 als lebendes Dokument + Ansicht in der App
Deploy App / deploy (push) Successful in 3m10s
Deploy App / deploy (push) Successful in 3m10s
Neue Funktionale/Technische Spezifikation (SPEZIFIKATION.md) mit Aenderungshistorie; ersetzt die bisherigen FDD/TDD-Dokumente v1-v5. In der App unter dem Menueintrag "SPEZIFIKATION" (Sidebar, unter den Plaenen) lesbar: /api/spec liefert das Markdown, SpecView rendert es. Styling ueber die bestehenden Farb-Tokens, folgt damit allen drei Schemata. Die MD-Datei war ueber .dockerignore (*.md) vom Image ausgeschlossen -> Ausnahme ergaenzt und Kopie in die Runner-Stage aufgenommen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
FileText,
|
||||
FolderKanban,
|
||||
LayoutDashboard,
|
||||
Menu,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { PlanView } from "@/components/PlanView";
|
||||
import { Dashboard } from "@/components/Dashboard";
|
||||
import { SpecView } from "@/components/SpecView";
|
||||
import { ProfileMenu } from "@/components/ProfileMenu";
|
||||
import { PlanProfileFields, emptyProfileDraft, type ProfileDraft } from "@/components/PlanProfileFields";
|
||||
import { api } from "@/lib/api-client";
|
||||
@@ -34,6 +36,8 @@ export function AppShell({ username }: { username: string }) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [showNewPlan, setShowNewPlan] = useState(false);
|
||||
const [showScenario, setShowScenario] = useState(false);
|
||||
// Die Spezifikation ist eine eigene Ansicht neben Uebersicht und Plan (schliessen sich aus).
|
||||
const [showSpec, setShowSpec] = useState(false);
|
||||
|
||||
const loadPlans = useCallback(async (preferId?: string) => {
|
||||
const data = await api.get<{ plans: PlanListItem[] }>("/api/plans");
|
||||
@@ -95,10 +99,13 @@ export function AppShell({ username }: { username: string }) {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedPlanId(null);
|
||||
setShowSpec(false);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
className={`flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium ${
|
||||
selectedPlanId === null ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
selectedPlanId === null && !showSpec
|
||||
? "bg-accent-soft text-accent-soft-fg"
|
||||
: "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
>
|
||||
<LayoutDashboard className="h-4 w-4" />
|
||||
@@ -123,10 +130,13 @@ export function AppShell({ username }: { username: string }) {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedPlanId(p.id);
|
||||
setShowSpec(false);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
className={`flex items-center gap-2 rounded-lg px-3 py-2 text-left text-sm ${
|
||||
selectedPlanId === p.id ? "bg-accent-soft font-medium text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
selectedPlanId === p.id && !showSpec
|
||||
? "bg-accent-soft font-medium text-accent-soft-fg"
|
||||
: "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
>
|
||||
<FolderKanban className="h-4 w-4 shrink-0" />
|
||||
@@ -134,6 +144,23 @@ export function AppShell({ username }: { username: string }) {
|
||||
<span className="text-[11px] text-faint">{p.phases.length}</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
<div className="mt-4 border-t border-border pt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowSpec(true);
|
||||
setSelectedPlanId(null);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm font-medium ${
|
||||
showSpec ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
>
|
||||
<FileText className="h-4 w-4 shrink-0" />
|
||||
SPEZIFIKATION
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
@@ -173,25 +200,30 @@ export function AppShell({ username }: { username: string }) {
|
||||
<Menu className="h-4 w-4" />
|
||||
</button>
|
||||
<h1 className="min-w-0 flex-1 truncate text-base font-semibold text-fg">
|
||||
{activePlan ? activePlan.name : "Uebersicht"}
|
||||
{showSpec ? "Spezifikation" : activePlan ? activePlan.name : "Uebersicht"}
|
||||
</h1>
|
||||
<ProfileMenu username={username} />
|
||||
</header>
|
||||
|
||||
<main className="flex-1 px-4 py-6 lg:px-8">
|
||||
{loading && <p className="text-sm text-muted">Laedt…</p>}
|
||||
{showSpec && <SpecView />}
|
||||
|
||||
{!loading && selectedPlanId === null && (
|
||||
{!showSpec && loading && <p className="text-sm text-muted">Laedt…</p>}
|
||||
|
||||
{!showSpec && !loading && selectedPlanId === null && (
|
||||
<DashboardHome
|
||||
username={username}
|
||||
plans={plans}
|
||||
onSelect={setSelectedPlanId}
|
||||
onSelect={(id) => {
|
||||
setSelectedPlanId(id);
|
||||
setShowSpec(false);
|
||||
}}
|
||||
onCreate={() => setShowNewPlan(true)}
|
||||
onDelete={handleDeletePlan}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!loading && detail && selectedPlanId && (
|
||||
{!showSpec && !loading && detail && selectedPlanId && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{detail.plan.phases.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user