diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index 836b7ff..cdc8759 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -131,9 +131,54 @@ struct SessionDetailView: View { } } + /// The ⋯ menu: the diff viewer for everyone, then the control-scope session actions. @ViewBuilder - private func sessionMenu(_ summary: WireSessionSummary) -> some View { + private var sessionMenu: some View { Menu { + Button { showDiff = true } label: { + Label(diffLabel, systemImage: "plus.forwardslash.minus") + } + if let summary, store.canControl { + Divider() + sessionActions(summary) + } + } label: { + Image(systemName: "ellipsis.circle") + } + } + + /// "View changes" with the live +/− counts when the host projects them, so the menu hints + /// whether there's anything to look at before opening the sheet. + private var diffLabel: String { + guard let stat = summary?.diffStat, stat.filesChanged > 0 else { return "View changes" } + return "View changes · +\(stat.added) −\(stat.removed)" + } + + /// The diff, presented as a sheet from the ⋯ menu — a viewer you visit, not a mode the + /// chat lives in. + private var diffSheet: some View { + NavigationStack { + Group { + if store.capabilities.canFetchDiff || store.openDiff != nil { + SessionDiffView(diff: store.openDiff, loading: store.diffLoading) + .onAppear { store.fetchDiff(sessionID) } + } else { + // Older host with no on-demand diff — the stat summary is all it projects. + DiffSummaryView(diffStat: summary?.diffStat) + } + } + .navigationTitle("Changes") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { showDiff = false } + } + } + } + } + + @ViewBuilder + private func sessionActions(_ summary: WireSessionSummary) -> some View { Button { renameDraft = summary.title; showRename = true } label: { Label("Rename", systemImage: "pencil") }