-
Keine weiteren Geräte registriert.
+
+
+ {{ hasRefreshed ? 'Keine weiteren Computer registriert.' : 'Noch keine Computer geladen.' }}
+
session.value?.deviceId ?? null);
const profile = ref(null);
const loading = ref(false);
+const hasRefreshed = ref(false);
const error = ref(null);
const sheetOpen = ref(false);
const selectedDevice = ref(null);
const platformInfo = ref<{ platform: string } | null>(null);
+// TODO: populate from backend once subscription/grace-period endpoint exists.
+const subscriptionInGracePeriod = ref(false);
+
// Share localHostname from protection composable with device status logic.
const localHostname = protection.localHostname;
-const { currentBackendDevice, otherDevices, iosStars } = useDeviceStatus(devices, localHostname, iphone, currentDeviceId);
+const { currentBackendDevice, iosDevices, desktopDevices, iosStars } =
+ useDeviceStatus(devices, localHostname, iphone, currentDeviceId);
+
+const selectedDeviceStars = computed(() => {
+ if (!selectedDevice.value || selectedDevice.value.platform !== "ios") return null;
+ if (!iphone.value) return null;
+ const modelMatch =
+ (selectedDevice.value.model ?? "").toLowerCase() === iphone.value.productType.toLowerCase();
+ const nameMatch =
+ (selectedDevice.value.name ?? "").toLowerCase() === iphone.value.name.toLowerCase();
+ if (!modelMatch && !nameMatch) return null;
+ return iosStars.value;
+});
onMounted(async () => {
await loadProfile();
- await refresh();
try {
const info = await getPlatform();
platformInfo.value = { platform: info.platform };
@@ -176,9 +213,31 @@ async function refresh() {
error.value = e?.message ?? "Geräte konnten nicht geladen werden";
} finally {
loading.value = false;
+ hasRefreshed.value = true;
}
}
+async function onIosSync(device: ComputedDevice) {
+ loading.value = true;
+ error.value = null;
+ try {
+ await protection.refreshIphone();
+ await protection.refreshBackendDevices();
+ // TODO: push missing MDM components and compare MDM version once backend exposes it.
+ } catch (e: any) {
+ error.value = e?.message ?? "Synchronisierung fehlgeschlagen";
+ } finally {
+ loading.value = false;
+ hasRefreshed.value = true;
+ }
+}
+
+async function onIosRemove(device: ComputedDevice) {
+ // TODO: call offboarding endpoint once backend provides it.
+ // For now this is a no-op placeholder to keep the UI safe.
+ console.log("[offboarding placeholder] remove ReBreak from", device.deviceId);
+}
+
function openDevice(device: ComputedDevice) {
selectedDevice.value = device;
sheetOpen.value = true;
diff --git a/docs/superpowers/plans/2026-06-16-magic-dashboard-ios-section.md b/docs/superpowers/plans/2026-06-16-magic-dashboard-ios-section.md
new file mode 100644
index 0000000..5fbcc2a
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-16-magic-dashboard-ios-section.md
@@ -0,0 +1,500 @@
+# Magic Dashboard iOS Section – Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Redesign the Magic dashboard so iOS devices are shown in a dedicated section under the desktop hero, with live USB status, action buttons, and sync/offboarding flows while keeping detection on-demand only.
+
+**Architecture:** A new `IosDeviceSection` component owns the iOS list. `useDeviceStatus` derives `iosDevices` and `desktopDevices` from the backend list. `IosDeviceCard` renders each device and its action button. `UnknownIosDeviceCard` handles USB-connected devices that are not registered in the backend. Existing `DeviceDetailSheet` is extended to show iOS stars for any connected iOS device.
+
+**Tech Stack:** Nuxt 3, Vue 3, Nuxt UI v4, Tauri 2, TypeScript, pnpm.
+
+---
+
+## Task 1: Extend `useDeviceStatus.ts` to split devices by platform
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/composables/useDeviceStatus.ts`
+
+**Why:** The dashboard needs separate `iosDevices` and `desktopDevices` lists instead of one mixed `otherDevices` list.
+
+- [ ] **Step 1: Add platform-based derived lists**
+
+Replace the single `otherDevices` derived with `iosDevices` and `desktopDevices`. Keep `otherDevices` as the union for backward compatibility or remove it if unused after status.vue update.
+
+```ts
+const iosDevices = computed(() =>
+ devices.value
+ .filter((d) => normalizePlatform(d.model ?? d.hostname) === "ios")
+ .map((d) => mapToComputedDevice(d, false)),
+);
+
+const desktopDevices = computed(() =>
+ devices.value
+ .filter((d) => {
+ const p = normalizePlatform(d.model ?? d.hostname);
+ return p === "mac" || p === "windows";
+ })
+ .filter((d) => d.deviceId !== currentBackendDevice.value?.deviceId)
+ .map((d) => mapToComputedDevice(d, false)),
+);
+```
+
+Introduce a small helper `mapToComputedDevice(d, isCurrent)` to avoid duplicating the mapping object.
+
+- [ ] **Step 2: Update return object**
+
+```ts
+return {
+ currentBackendDevice,
+ iosDevices,
+ desktopDevices,
+ otherDevices: desktopDevices, // temporary alias until status.vue is updated
+ iosStars,
+};
+```
+
+- [ ] **Step 3: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+Expected: same pre-existing errors as before, no new ones.
+
+---
+
+## Task 2: Create `IosDeviceCard.vue`
+
+**Files:**
+- Create: `apps/rebreak-magic/app/components/IosDeviceCard.vue`
+
+**Why:** Each backend iOS device needs its own card with status, stars (if USB-connected), and a context-aware action button.
+
+- [ ] **Step 1: Write the component**
+
+Props:
+
+```ts
+const props = defineProps<{
+ device: ComputedDevice;
+ iphone: IphoneDeviceState | null;
+ isConnected: boolean;
+}>();
+```
+
+Compute `iosStars` from `iphone` when connected. Derive the action label/target from `device` and `iosStars`:
+
+```ts
+const action = computed(() => {
+ if (!props.isConnected || !props.iphone) {
+ return { label: "Verbinden", to: "/detect", icon: "i-heroicons-link" };
+ }
+ if (!props.iphone.isSupervised) {
+ return { label: "Supervisen", to: "/supervise", icon: "i-heroicons-shield-check" };
+ }
+ if (!props.iphone.installedProfileIDs?.includes("org.rebreak.mdm.enrollment")) {
+ return { label: "Enrollen", to: "/enroll", icon: "i-heroicons-document-check" };
+ }
+ if (!props.iphone.installedProfileIDs?.includes("org.rebreak.protection.contentfilter.sideload")) {
+ return { label: "Sideload installieren", to: "/sideload", icon: "i-heroicons-lock-closed" };
+ }
+ if (!props.iphone.installedAppBundleIDs?.includes("org.rebreak.app")) {
+ return { label: "App installieren", to: "/sideload", icon: "i-heroicons-arrow-down-tray" };
+ }
+ return { label: "Synchronisieren", icon: "i-heroicons-arrow-path" };
+});
+```
+
+Template: device icon, name/model, status badge, optional `IosStarRating`, last-seen text, and the action button. If the label is "Synchronisieren" use `@click` to emit `sync`; otherwise use `to` navigation.
+
+- [ ] **Step 2: Add emits**
+
+```ts
+const emit = defineEmits<{
+ (e: "sync", device: ComputedDevice): void;
+ (e: "open", device: ComputedDevice): void;
+}>();
+```
+
+- [ ] **Step 3: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+Expected: no new errors.
+
+---
+
+## Task 3: Create `UnknownIosDeviceCard.vue`
+
+**Files:**
+- Create: `apps/rebreak-magic/app/components/UnknownIosDeviceCard.vue`
+
+**Why:** A USB-connected iOS device that is not registered to the user's ReBreak account must be shown as unrecognizable with a clear next-step message.
+
+- [ ] **Step 1: Write the component**
+
+Props:
+
+```ts
+const props = defineProps<{
+ iphone: IphoneDeviceState;
+}>();
+```
+
+Template: warning icon, title "Dieses iPhone ist nicht erkennbar", model/iOS version/UDID as read-only info, and helper text:
+
+> "Mit keinem ReBreak-Konto verbunden. Um es zu verwalten: ReBreak-App installieren, anmelden und Gerät registrieren."
+
+No action buttons.
+
+- [ ] **Step 2: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 4: Create `IosDeviceSection.vue`
+
+**Files:**
+- Create: `apps/rebreak-magic/app/components/IosDeviceSection.vue`
+
+**Why:** This component owns the iOS section header, list, matching logic, and empty/unknown states.
+
+- [ ] **Step 1: Write the component**
+
+Props:
+
+```ts
+const props = defineProps<{
+ devices: ComputedDevice[];
+ iphone: IphoneDeviceState | null;
+ loading: boolean;
+ hasRefreshed: boolean;
+}>();
+
+const emit = defineEmits<{
+ (e: "sync", device: ComputedDevice): void;
+ (e: "open", device: ComputedDevice): void;
+}>();
+```
+
+Implement matching helper:
+
+```ts
+function matchesIphone(device: ComputedDevice, iphone: IphoneDeviceState): boolean {
+ const modelMatch = (device.model ?? "").toLowerCase() === iphone.productType.toLowerCase();
+ const nameMatch = (device.name ?? "").toLowerCase() === iphone.name.toLowerCase();
+ return modelMatch || nameMatch;
+}
+```
+
+Compute:
+
+```ts
+const connectedDeviceId = computed(() => {
+ if (!props.iphone) return null;
+ return props.devices.find((d) => matchesIphone(d, props.iphone!))?.deviceId ?? null;
+});
+
+const hasUnknownUsbDevice = computed(() => {
+ return !!props.iphone && !connectedDeviceId.value;
+});
+```
+
+Template:
+
+- Section title "Meine iOS-Geräte"
+- If `!hasRefreshed && devices.length === 0`: "Noch keine iOS-Geräte geladen."
+- If `hasRefreshed && devices.length === 0`: "Keine iOS-Geräte registriert. ReBreak-App installieren und Gerät hinzufügen."
+- If `hasUnknownUsbDevice`: render `UnknownIosDeviceCard` first.
+- Render `IosDeviceCard` for each device with `isConnected = device.deviceId === connectedDeviceId`.
+
+- [ ] **Step 2: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 5: Update `status.vue`
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/pages/status.vue`
+
+**Why:** The page must render the new iOS section and use `desktopDevices` instead of `otherDevices` for the remaining list.
+
+- [ ] **Step 1: Replace `otherDevices` usage with `iosDevices` and `desktopDevices`**
+
+Update import from `useDeviceStatus`:
+
+```ts
+const { currentBackendDevice, iosDevices, desktopDevices, iosStars } =
+ useDeviceStatus(devices, localHostname, iphone, currentDeviceId);
+```
+
+- [ ] **Step 2: Insert iOS section under hero**
+
+After the hero section and before "Weitere Geräte", add:
+
+```vue
+
+```
+
+- [ ] **Step 3: Change "Weitere Geräte" list to `desktopDevices`**
+
+Replace `otherDevices` references in the list with `desktopDevices`. Update empty copy to "Keine weiteren Computer geladen." / "Keine weiteren Computer registriert." depending on `hasRefreshed`.
+
+- [ ] **Step 4: Add `onIosSync` handler**
+
+```ts
+async function onIosSync(device: ComputedDevice) {
+ loading.value = true;
+ error.value = null;
+ try {
+ await protection.refreshIphone();
+ // TODO: push missing MDM components and compare MDM version once the backend exposes it.
+ await protection.refreshBackendDevices();
+ } catch (e: any) {
+ error.value = e?.message ?? "Synchronisierung fehlgeschlagen";
+ } finally {
+ loading.value = false;
+ hasRefreshed.value = true;
+ }
+}
+```
+
+Expose `refreshIphone` and `refreshBackendDevices` from `useProtectionStatus` if not already exported.
+
+- [ ] **Step 5: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+Expected: no new errors.
+
+---
+
+## Task 6: Extend `useProtectionStatus.ts` exports
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/composables/useProtectionStatus.ts`
+
+**Why:** `status.vue` needs to call `refreshIphone` and `refreshBackendDevices` independently for the sync action.
+
+- [ ] **Step 1: Export the two refresh functions**
+
+Add to the return object:
+
+```ts
+return {
+ // ... existing returns
+ refreshIphone,
+ refreshBackendDevices,
+};
+```
+
+- [ ] **Step 2: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 7: Adjust `DeviceDetailSheet.vue` for iOS stars
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/components/DeviceDetailSheet.vue`
+
+**Why:** iOS devices are never `isCurrent`, but the sheet should still show stars when the opened device is connected via USB.
+
+- [ ] **Step 1: Change `showIosStars` condition**
+
+Accept a new prop or use the existing `iosStars` prop directly. The current condition is:
+
+```ts
+const showIosStars = computed(() => props.device?.isCurrent && props.device?.platform === "ios");
+```
+
+Change to:
+
+```ts
+const showIosStars = computed(() => props.device?.platform === "ios" && !!props.iosStars);
+```
+
+Ensure the parent passes `iosStars` for the opened iOS device when it is connected.
+
+- [ ] **Step 2: Hide desktop-only sections for iOS devices**
+
+`showDesktopToggle` should remain as is (only mac/windows + isCurrent).
+Cooldown controls should only show for `device.isCurrent` (desktop), which is already the case.
+
+- [ ] **Step 3: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 8: Update `DeviceHeroCard.vue` and `DeviceListItem.vue`
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/components/DeviceHeroCard.vue`
+- Modify: `apps/rebreak-magic/app/components/DeviceListItem.vue`
+
+**Why:** These components are now desktop-only. Remove iOS-specific rendering if it is no longer needed or keep it defensive.
+
+- [ ] **Step 1: In `DeviceHeroCard.vue`, keep `showIosStars` defensive**
+
+No functional change needed because the hero only receives desktop devices, but confirm `showIosStars` still computes correctly.
+
+- [ ] **Step 2: In `DeviceListItem.vue`, no change required**
+
+It will only be rendered with desktop devices.
+
+---
+
+## Task 9: Add MDM version awareness (frontend foundation)
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/composables/useTauri.ts`
+- Modify: `apps/rebreak-magic/app/components/IosDeviceCard.vue`
+
+**Why:** The sync action must later compare installed MDM version with the latest ReBreak MDM version.
+
+- [ ] **Step 1: Add a constant and helper in `useTauri.ts`**
+
+```ts
+export const REBREAK_MDM_VERSION = "0.1";
+
+export function getInstalledMdmVersion(installedProfileIDs: string[]): string | null {
+ const versionId = installedProfileIDs.find((id) => id.startsWith("org.rebreak.mdm.version."));
+ return versionId?.replace("org.rebreak.mdm.version.", "") ?? null;
+}
+```
+
+- [ ] **Step 2: Use it in `IosDeviceCard.vue` action logic**
+
+When connected and all core checks pass, compare `getInstalledMdmVersion(...)` with `REBREAK_MDM_VERSION`. If outdated or missing, return `{ label: "MDM-Update installieren", icon: "i-heroicons-arrow-up-tray" }` and emit `sync`.
+
+- [ ] **Step 3: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 10: Grace-period / Offboarding placeholder
+
+**Files:**
+- Modify: `apps/rebreak-magic/app/components/IosDeviceCard.vue`
+- Modify: `apps/rebreak-magic/app/pages/status.vue`
+
+**Why:** The spec requires a "ReBreak entfernen" action during the 3-day grace period after cancellation. The backend endpoint does not exist yet, so we add a safe placeholder.
+
+- [ ] **Step 1: Add `subscriptionInGracePeriod` prop**
+
+```ts
+const props = defineProps<{
+ device: ComputedDevice;
+ iphone: IphoneDeviceState | null;
+ isConnected: boolean;
+ inGracePeriod?: boolean;
+}>();
+```
+
+- [ ] **Step 2: Show offboarding button when in grace period**
+
+At the top of the action derivation:
+
+```ts
+if (props.inGracePeriod) {
+ return { label: "ReBreak entfernen", icon: "i-heroicons-trash", variant: "danger" };
+}
+```
+
+Emit a new `remove` event. The parent shows a placeholder toast or logs until the backend endpoint is ready.
+
+- [ ] **Step 3: Stub grace-period state in `status.vue`**
+
+```ts
+const subscriptionInGracePeriod = ref(false);
+// TODO: populate from backend once subscription status endpoint exists.
+```
+
+Pass it to `IosDeviceSection` and down to each card.
+
+- [ ] **Step 4: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+
+---
+
+## Task 11: Verification and build
+
+**Files:** n/a
+
+**Why:** Ensure the frontend compiles and the Tauri bundle can be built.
+
+- [ ] **Step 1: Typecheck**
+
+Run: `cd apps/rebreak-magic && pnpm nuxi typecheck`
+Expected: only pre-existing errors.
+
+- [ ] **Step 2: Build Tauri bundle**
+
+Run: `cd apps/rebreak-magic && pnpm tauri:build`
+Expected: completes without new frontend errors. This may take several minutes on first run.
+
+- [ ] **Step 3: Manual smoke test**
+
+Launch the built app with the debug pairing code `000000`, open the dashboard, click **Aktualisieren**, and confirm:
+
+- Desktop hero still renders.
+- iOS section appears.
+- If no iOS devices: correct empty message.
+- If a USB iPhone is connected and registered: stars and action button render.
+
+---
+
+## Task 12: Commit changes
+
+**Files:** n/a
+
+- [ ] **Step 1: Stage and commit**
+
+```bash
+git add apps/rebreak-magic/app/composables/useDeviceStatus.ts \
+ apps/rebreak-magic/app/composables/useProtectionStatus.ts \
+ apps/rebreak-magic/app/composables/useTauri.ts \
+ apps/rebreak-magic/app/components/IosDeviceSection.vue \
+ apps/rebreak-magic/app/components/IosDeviceCard.vue \
+ apps/rebreak-magic/app/components/UnknownIosDeviceCard.vue \
+ apps/rebreak-magic/app/components/DeviceDetailSheet.vue \
+ apps/rebreak-magic/app/components/DeviceHeroCard.vue \
+ apps/rebreak-magic/app/components/DeviceListItem.vue \
+ apps/rebreak-magic/app/pages/status.vue \
+ docs/superpowers/specs/2026-06-16-magic-dashboard-ios-section-design.md \
+ docs/superpowers/plans/2026-06-16-magic-dashboard-ios-section.md
+
+git commit -m "feat(magic): dedicated iOS section in dashboard with on-demand sync"
+```
+
+---
+
+## Spec Coverage Check
+
+| Spec requirement | Task |
+|---|---|
+| Desktop hero remains | Task 5 |
+| Dedicated iOS section under hero | Tasks 4, 5 |
+| Backend iOS devices listed | Tasks 1, 4 |
+| USB live status synced to matching device | Tasks 2, 4 |
+| Unknown USB device shown as unrecognizable | Task 3 |
+| Action buttons for supervise/enroll/sideload/app/sync | Task 2 |
+| On-demand detection preserved | Task 5, existing code |
+| Grace-period offboarding placeholder | Task 10 |
+| MDM version foundation | Task 9 |
+| DeviceDetailSheet iOS stars | Task 7 |
+
+## Known Backend Dependencies (out of scope for this frontend plan)
+
+- Subscription cancellation / grace-period endpoint.
+- Offboarding endpoint: remove MDM profiles, unsupervise, clean DB entry.
+- Central `REBREAK_MDM_VERSION` value injected into MDM enrollment profiles.
diff --git a/docs/superpowers/specs/2026-06-16-magic-dashboard-ios-section-design.md b/docs/superpowers/specs/2026-06-16-magic-dashboard-ios-section-design.md
new file mode 100644
index 0000000..1e44072
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-16-magic-dashboard-ios-section-design.md
@@ -0,0 +1,167 @@
+# Magic Dashboard – iOS-Section Redesign
+
+## Ziel
+
+Das Magic-Dashboard soll klar zwischen zwei eigenen Bausteinen trennen:
+
+1. **Desktop-Schutz** (Mac/Windows) – das Gerät, auf dem Magic läuft.
+2. **iOS-Verwaltung** (eigene iPhone/iPad-Geräte) – registrierte iOS-Geräte des Users mit Status, Sternen und passenden Aktionen.
+
+„Andere Geräte“ bleiben als sekundäre Information erhalten, sollen aber nicht den Fokus stehlen. Magic ist kein offenes Verwaltungstool für fremde Geräte.
+
+---
+
+## Annahmen (aus dem Abstimmungsgespräch)
+
+- Backend-iOS-Geräte werden immer gelistet.
+- Wenn ein iPhone/iPad per USB verbunden ist, werden Live-Daten (Supervision, Enrollment, Sideload, App) mit dem passenden Backend-Eintrag synchronisiert.
+- Ein per USB verbundenes, aber im Backend unbekanntes iOS-Gerät wird als **„Nicht erkennbar“** markiert. Der Hinweis verweist auf: ReBreak-App installieren → anmelden → Gerät registrieren. Erst danach ist es verwaltbar.
+- Supervise / Enroll / Sideload / App-Install bleiben im bestehenden Wizard (`/detect`, `/supervise`, `/enroll`, `/sideload`). Das Dashboard bietet nur den passenden Einstieg.
+
+---
+
+## Seitenstruktur
+
+```
+status.vue
+├── Header (Profil, Logout)
+├── Section: Aktives Gerät (Desktop-Hero)
+│ └── DeviceHeroCard für currentBackendDevice (mac/windows)
+├── Section: Meine iOS-Geräte
+│ ├── UnknownIosDeviceCard (falls USB-Device nicht im Backend)
+│ └── IosDeviceCard[] für jedes Backend-iOS-Gerät
+│ (Sterne + Status + Action-Button)
+├── Section: Weitere Geräte
+│ └── DeviceListItem[] für sonstige Backend-Geräte
+└── DeviceDetailSheet (weiterhin für Details/Cooldown)
+```
+
+---
+
+## Neue Komponenten
+
+### `IosDeviceSection.vue`
+
+- Props:
+ - `devices: ComputedDevice[]` – alle Backend-iOS-Geräte des Users
+ - `iphone: IphoneDeviceState | null` – aktuell per USB erkanntes Gerät
+ - `loading: boolean`
+- Zeigt den Section-Header und rendert die Liste von `IosDeviceCard`s.
+- Wenn `iphone` verbunden, aber kein passendes Backend-Gerät gefunden wird, wird `UnknownIosDeviceCard` angezeigt.
+- Wenn noch keine Backend-iOS-Geräte geladen wurden: Hinweis „Keine iOS-Geräte geladen“ + Aktualisieren-Button.
+- Wenn geladen und leer: Hinweis „Keine iOS-Geräte registriert. ReBreak-App installieren und Gerät hinzufügen.“
+
+### `IosDeviceCard.vue`
+
+- Props:
+ - `device: ComputedDevice`
+ - `iphone: IphoneDeviceState | null` – falls dieses Gerät per USB verbunden ist
+ - `isConnected: boolean`
+- Zeigt:
+ - Name/Modell
+ - Status-Badge (`active`, `pending`, `unprotected` etc.)
+ - Letzte Sichtung
+ - `IosStarRating` + detaillierte Sternen-Liste, wenn `isConnected`
+ - Sonst Hinweis: „Zum Live-Status iPhone per USB verbinden“
+- Action-Button (ableitet sich aus dem gemergten Zustand):
+ - Nicht supervised → „Supervisen" → `/supervise`
+ - Supervised, aber Enrollment fehlt → „Enrollen" → `/enroll`
+ - Enrollment vorhanden, aber Sideload-Profil fehlt → „Sideload installieren" → `/sideload`
+ - Sideload vorhanden, aber App fehlt → „App installieren" (MDM-Befehl oder Link)
+ - Alles okay → „Synchronisieren" (prüft Enrollment-, Sideload- und Supervision-Status; bei Abweichungen werden fehlende Profile/MDM-Kommandos gepusht; falls die lokale MDM-Version hinter der aktuellen ReBreak-MDM-Version zurückfällt, wird ein Update gepusht und der User informiert)
+ - Während der 3-Tage-Kündigungs-Grace-Period → „ReBreak entfernen" (löst Offboarding aus: MDM-Profile entfernen, Gerät unsupervised setzen, Eintrag bereinigen)
+
+### `UnknownIosDeviceCard.vue`
+
+- Props:
+ - `iphone: IphoneDeviceState`
+- Zeigt:
+ - Warn-Icon + „Dieses iPhone ist nicht erkennbar"
+ - Modell, iOS-Version, UDID (nur zur Info)
+ - Hinweis: „Mit keinem ReBreak-Konto verbunden. Um es zu verwalten: ReBreak-App installieren, anmelden und Gerät registrieren."
+ - Keine Supervise-/Enroll-Aktionen.
+
+---
+
+## Datenfluss
+
+1. User klickt in `status.vue` auf **Aktualisieren**.
+2. `protection.refresh()` wird aufgerufen:
+ - `detectIphoneState()` lädt das per USB verbundene iOS-Gerät.
+ - `getMagicDevices()` lädt alle Backend-Geräte in den shared `useMagicDevices`-State.
+3. `useDeviceStatus` liefert weiterhin:
+ - `currentBackendDevice` (Desktop)
+ - `otherDevices` (alles außer current)
+4. `IosDeviceSection` erhält die Liste aller `otherDevices`, filtert intern auf `platform === 'ios'` und versucht, das verbundene `iphone` per Modell + Name zuzuordnen.
+5. Action-Buttons leiten den User basierend auf dem gemergten Live-Status in den passenden Wizard-Schritt weiter.
+
+### iOS-Matching
+
+Da Backend-`deviceId` (Capacitor-UUID) nicht mit USB-UDID übereinstimmt, erfolgt das Matching über:
+
+- `device.model` (Backend) ↔ `iphone.productType` (USB)
+- `device.name` (Backend) ↔ `iphone.name` (USB) als Fallback / Verfeinerung
+
+Sind mehrere Geräte mit identischem Modell vorhanden, wird das erste passende (`name` match) als verbunden markiert; bei Unklarheit wird das `iphone` nicht zugeordnet und erscheint als `UnknownIosDeviceCard`.
+
+---
+
+## Bestehende Komponenten – Anpassungen
+
+### `DeviceHeroCard.vue`
+
+- Keine iOS-Sterne mehr anzeigen (`showIosStars` bleibt aber für zukünftige Flexibilität).
+- Aktionen bleiben auf Desktop-Schutz beschränkt.
+
+### `DeviceListItem.vue`
+
+- Wird für „Weitere Geräte“ (andere Desktops) weiterverwendet.
+- iOS-Geräte verschwinden aus dieser Liste und werden in der neuen iOS-Section angezeigt.
+
+### `DeviceDetailSheet.vue`
+
+- iOS-Sterne-Anzeige gilt für jedes iOS-Gerät, das gerade per USB verbunden ist (nicht nur `isCurrent`, da iOS-Geräte nie „current" sind).
+- Cooldown-Steuerung bleibt nur für `isCurrent`-Desktop-Geräte.
+
+### `useDeviceStatus.ts`
+
+- Entfernt den Debug-`watchEffect` (bereits erledigt).
+- Fügt optional `iosDevices` und `desktopDevices` als getrennte Derived Lists hinzu, damit `status.vue` weniger Filter-Logik enthält.
+
+---
+
+## On-Demand-Verhalten bleibt erhalten
+
+- Kein automatisches Polling mehr.
+- Sterne/Status werden nur beim manuellen Refresh aktualisiert.
+- Das verhindert erneut Log-Spam durch wiederholte `detect_iphone_state`-Aufrufe.
+
+---
+
+## Fehlerbehandlung
+
+- Wenn `detectIphoneState` fehlschlägt: Fehler nur in `protection.lastError`; iOS-Section zeigt Backend-Liste weiterhin an.
+- Wenn `getMagicDevices` fehlschägt: `error`-Banner in `status.vue`.
+- Wenn Matching mehrdeutig: `UnknownIosDeviceCard` statt falscher Zuordnung.
+
+---
+
+## Kündigungs-Grace-Period & Offboarding
+
+- Solange das Gerät enrolled ist **und** das Abo aktiv ist, wird **nichts** deinstalliert.
+- Nach einer Kündigung bleibt das iOS-Gerät für **3 Tage** in der Liste sichtbar.
+- Der Button **„ReBreak entfernen"** ist **unsichtbar oder disabled**, solange die Grace-Period noch nicht begonnen hat.
+- Sobald die Grace-Period läuft, erscheint der Button ohne zusätzliche Sicherheitsabfrage.
+- Ein Klick darauf startet das Offboarding:
+ 1. MDM-Enrollment-Profil und Sideload-Profil vom Gerät entfernen.
+ 2. Gerät aus dem Supervised-Modus zurücksetzen.
+ 3. Backend-Eintrag für das iOS-Gerät bereinigen.
+- **Backend-Abhängigkeit:** Es braucht ein Feld/Endpoint, der die Kündigung + verbleibende Grace-Period erkennbar macht (z. B. `subscriptionCancelledAt` im Profil oder dedizierter `/api/magic/subscription-status`). Das Offboarding selbst braucht einen neuen API-Endpoint oder Tauri-Command, der MDM-Remove + Unsupervise orchestriert.
+
+---
+
+## Offene Punkte / Nächste Schritte
+
+1. Existiert bereits ein Backend-Feld/Endpoint für Kündigung + Grace-Period, oder muss der gebaut werden?
+2. Wie wird die „aktuelle ReBreak-MDM-Version" bestimmt – ist sie im Profil hinterlegt, im Backend konfiguriert oder über eine Tauri-Funktion verfügbar?
+3. Soll die App-Installation via MDM direkt aus dem Dashboard auslösbar sein, oder reicht ein Verweis auf `/sideload`?