nvrsion: Add: Align streak card lightning bolt to left side of card, remove streak freeze counter, fix push notification wording to reflect correct session status, adjust push notification logic to recall only the approval request’s notification without affecting live activities, and investigate Live Activity Session Load.

Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-06 15:10:07 -07:00
co-authored by Nucleic
parent 43a9784aa1
commit 844549b443
11 changed files with 612 additions and 13 deletions
@@ -131,15 +131,42 @@ private struct LockScreenView: View {
private struct SessionRow: View {
let line: NucleicSessionAttributes.SessionLine
/// An approval this row can resolve inline: it's blocked on an approval, carries that approval's
/// id, and isn't high-risk (§3.3 — high-risk keeps the plain tap-to-open row and is answered on
/// the app's guarded card). `nil` until the producer populates `approvalID` — an older host, or
/// today's summary that doesn't yet carry it, so the row stays a deep-link exactly as before.
private var inlineApprovalID: String? {
guard line.kind == .approval, (line.approvalIsHighRisk ?? false) == false else { return nil }
return line.approvalID
}
var body: some View {
if let url = NucleicDeepLink.session(line.id) {
if let approvalID = inlineApprovalID {
actionableRow(approvalID: approvalID)
} else if let url = NucleicDeepLink.session(line.id) {
Link(destination: url) { rowContent }
} else {
rowContent
}
}
private var rowContent: some View {
/// An approval row with inline Allow/Deny. The identity still deep-links to the session; the
/// buttons fire `ApproveFromActivityIntent`, which the system performs in the app's background
/// process — allow/deny without unlocking (§4.1).
private func actionableRow(approvalID: String) -> some View {
HStack(spacing: 8) {
if let url = NucleicDeepLink.session(line.id) {
Link(destination: url) { rowIdentity }
} else {
rowIdentity
}
Spacer(minLength: 6)
ApprovalButtons(sessionID: line.id, approvalID: approvalID)
}
}
/// The glyph + title + "project · Backend" — the row's identity, minus the trailing status.
private var rowIdentity: some View {
HStack(spacing: 8) {
Image(systemName: ActivityPalette.glyph(line.kind))
.font(.footnote)
@@ -159,6 +186,12 @@ private struct SessionRow: View {
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}
private var rowContent: some View {
HStack(spacing: 8) {
rowIdentity
Spacer(minLength: 6)
Text(line.detail)
.font(.caption2.weight(.medium))
@@ -169,6 +202,33 @@ private struct SessionRow: View {
}
}
/// The compact inline Allow/Deny pair for an actionable approval row. Each button fires
/// `ApproveFromActivityIntent` (a low/medium-risk decision — high-risk never reaches here).
private struct ApprovalButtons: View {
let sessionID: String
let approvalID: String
var body: some View {
HStack(spacing: 6) {
Button(intent: intent(allow: false)) {
Image(systemName: "xmark").font(.caption2.bold())
}
.tint(ActivityPalette.danger)
Button(intent: intent(allow: true)) {
Image(systemName: "checkmark").font(.caption2.bold())
}
.tint(ActivityPalette.success)
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
private func intent(allow: Bool) -> ApproveFromActivityIntent {
ApproveFromActivityIntent(
approvalID: approvalID, sessionID: sessionID, isHighRisk: false, allow: allow)
}
}
/// Headline count chips: running (blue), waiting (teal), approvals (amber). Only nonzero show.
private struct CountChips: View {
let state: NucleicSessionAttributes.ContentState