diff --git a/src/client/features/legislation/components/legislation-detail.tsx b/src/client/features/legislation/components/legislation-detail.tsx
new file mode 100644
index 0000000..1bb9841
--- /dev/null
+++ b/src/client/features/legislation/components/legislation-detail.tsx
@@ -0,0 +1,89 @@
+import { useState } from "react"
+import { useLegislation } from "../hooks/use-legislation"
+import { useUserVote } from "../hooks/use-user-vote"
+import { VoteWidget } from "./vote-widget"
+
+interface LegislationDetailProps {
+ legislationId: number
+}
+
+export function LegislationDetail({ legislationId }: LegislationDetailProps) {
+ const { legislation, loading, error } = useLegislation(legislationId)
+ const { vote, castVote } = useUserVote(legislationId)
+ const [showFullText, setShowFullText] = useState(false)
+
+ if (loading) {
+ return
Laden…
+ }
+
+ if (error || !legislation) {
+ return (
+
+ {error ?? "Gesetzesvorlage nicht gefunden"}
+
+ )
+ }
+
+ return (
+
+
+
+ {legislation.title}
+
+ {legislation.beratungsstand && (
+
+ {legislation.beratungsstand}
+
+ )}
+
+
+ {/* Summary or abstract */}
+
+ {legislation.summary ? (
+
{legislation.summary}
+ ) : legislation.abstract ? (
+
+ {legislation.abstract}
+
+ ) : (
+
+ Keine Zusammenfassung verfügbar
+
+ )}
+
+
+ {/* Full text toggle */}
+ {legislation.fullText && (
+
+
+ {showFullText && (
+
+ {legislation.fullText}
+
+ )}
+
+ )}
+
+ {/* Vote section */}
+
+
Dein Vote
+
+ {vote && (
+
+ Du hast mit{" "}
+
+ {vote === "ja" ? "Ja" : vote === "nein" ? "Nein" : "Enthaltung"}
+ {" "}
+ gestimmt. Du kannst deine Stimme jederzeit ändern.
+
+ )}
+
+
+ )
+}
diff --git a/src/client/features/legislation/components/vote-widget.tsx b/src/client/features/legislation/components/vote-widget.tsx
new file mode 100644
index 0000000..2ff082a
--- /dev/null
+++ b/src/client/features/legislation/components/vote-widget.tsx
@@ -0,0 +1,42 @@
+import type { UserVoteChoice } from "../../../../shared/legislation-types"
+
+interface VoteWidgetProps {
+ currentVote: UserVoteChoice | null
+ onVote: (choice: UserVoteChoice) => void
+ disabled?: boolean
+}
+
+const choices: { value: UserVoteChoice; label: string; color: string }[] = [
+ { value: "ja", label: "Ja", color: "bg-green-600 hover:bg-green-700" },
+ { value: "nein", label: "Nein", color: "bg-red-600 hover:bg-red-700" },
+ {
+ value: "enthaltung",
+ label: "Enthaltung",
+ color: "bg-gray-500 hover:bg-gray-600",
+ },
+]
+
+export function VoteWidget({ currentVote, onVote, disabled }: VoteWidgetProps) {
+ return (
+
+ {choices.map((c) => {
+ const isSelected = currentVote === c.value
+ return (
+
+ )
+ })}
+
+ )
+}
diff --git a/src/client/features/legislation/hooks/use-legislation.ts b/src/client/features/legislation/hooks/use-legislation.ts
new file mode 100644
index 0000000..4f0b321
--- /dev/null
+++ b/src/client/features/legislation/hooks/use-legislation.ts
@@ -0,0 +1,37 @@
+import { useEffect, useState } from "react"
+import type { LegislationDetail } from "../../../../shared/legislation-types"
+import { fetchLegislation } from "../lib/legislation-api"
+
+interface UseLegislationReturn {
+ legislation: LegislationDetail | null
+ loading: boolean
+ error: string | null
+}
+
+export function useLegislation(legislationId: number): UseLegislationReturn {
+ const [legislation, setLegislation] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ let cancelled = false
+
+ async function load() {
+ try {
+ const data = await fetchLegislation(legislationId)
+ if (!cancelled) setLegislation(data)
+ } catch (err) {
+ if (!cancelled) setError(String(err))
+ } finally {
+ if (!cancelled) setLoading(false)
+ }
+ }
+
+ load()
+ return () => {
+ cancelled = true
+ }
+ }, [legislationId])
+
+ return { legislation, loading, error }
+}
diff --git a/src/client/features/legislation/hooks/use-user-vote.ts b/src/client/features/legislation/hooks/use-user-vote.ts
new file mode 100644
index 0000000..377afc9
--- /dev/null
+++ b/src/client/features/legislation/hooks/use-user-vote.ts
@@ -0,0 +1,66 @@
+import { useDb } from "@/shared/db/provider"
+import { useDeviceId } from "@/shared/hooks/use-device-id"
+import { useCallback, useEffect, useState } from "react"
+import type { UserVoteChoice } from "../../../../shared/legislation-types"
+import { castVote as apiCastVote, fetchUserVote } from "../lib/legislation-api"
+import { getUserVote, saveUserVote } from "../lib/user-votes-db"
+
+interface UseUserVoteReturn {
+ vote: UserVoteChoice | null
+ loading: boolean
+ castVote: (choice: UserVoteChoice) => Promise
+}
+
+export function useUserVote(legislationId: number): UseUserVoteReturn {
+ const db = useDb()
+ const deviceId = useDeviceId()
+ const [vote, setVote] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ let cancelled = false
+
+ async function load() {
+ // load from local DB first for instant UI
+ const local = await getUserVote(db, legislationId)
+ if (!cancelled && local) {
+ setVote(local.vote as UserVoteChoice)
+ setLoading(false)
+ }
+
+ // sync from backend if device ID is available
+ if (deviceId) {
+ const remote = await fetchUserVote(legislationId, deviceId).catch(
+ () => null,
+ )
+ if (!cancelled && remote) {
+ setVote(remote.vote)
+ await saveUserVote(db, legislationId, remote.vote)
+ }
+ }
+
+ if (!cancelled) setLoading(false)
+ }
+
+ load()
+ return () => {
+ cancelled = true
+ }
+ }, [db, legislationId, deviceId])
+
+ const cast = useCallback(
+ async (choice: UserVoteChoice) => {
+ // optimistic local update
+ setVote(choice)
+ await saveUserVote(db, legislationId, choice)
+
+ // sync to backend
+ if (deviceId) {
+ await apiCastVote(legislationId, deviceId, choice)
+ }
+ },
+ [db, legislationId, deviceId],
+ )
+
+ return { vote, loading, castVote: cast }
+}
diff --git a/src/client/features/legislation/index.ts b/src/client/features/legislation/index.ts
new file mode 100644
index 0000000..114dacb
--- /dev/null
+++ b/src/client/features/legislation/index.ts
@@ -0,0 +1 @@
+export { LegislationDetail } from "./components/legislation-detail"
diff --git a/src/client/routes/app/legislation.$legislationId.tsx b/src/client/routes/app/legislation.$legislationId.tsx
new file mode 100644
index 0000000..72ba4e1
--- /dev/null
+++ b/src/client/routes/app/legislation.$legislationId.tsx
@@ -0,0 +1,11 @@
+import { LegislationDetail } from "@/features/legislation"
+import { createFileRoute } from "@tanstack/react-router"
+
+function LegislationPage() {
+ const { legislationId } = Route.useParams()
+ return
+}
+
+export const Route = createFileRoute("/app/legislation/$legislationId")({
+ component: LegislationPage,
+})