@hasna/recordings 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +349 -69
  2. package/bun.lock +3 -0
  3. package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
  4. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
  5. package/dist/cli/index.js +1728 -320
  6. package/dist/cli/macos-permissions.d.ts +13 -0
  7. package/dist/cli/macos-permissions.d.ts.map +1 -0
  8. package/dist/cli/options.d.ts +12 -0
  9. package/dist/cli/options.d.ts.map +1 -1
  10. package/dist/db/pg-migrations.d.ts.map +1 -1
  11. package/dist/db/recordings.d.ts +2 -1
  12. package/dist/db/recordings.d.ts.map +1 -1
  13. package/dist/db/remote-storage.d.ts +5 -1
  14. package/dist/db/remote-storage.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +628 -147
  18. package/dist/lib/bun-runtime.d.ts +11 -0
  19. package/dist/lib/bun-runtime.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +8 -1
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/enhancer.d.ts +10 -2
  23. package/dist/lib/enhancer.d.ts.map +1 -1
  24. package/dist/lib/install-maintenance.d.ts +24 -0
  25. package/dist/lib/install-maintenance.d.ts.map +1 -0
  26. package/dist/lib/machine.d.ts +2 -0
  27. package/dist/lib/machine.d.ts.map +1 -0
  28. package/dist/lib/recorder.d.ts +2 -1
  29. package/dist/lib/recorder.d.ts.map +1 -1
  30. package/dist/lib/recording-create-identity.d.ts +13 -0
  31. package/dist/lib/recording-create-identity.d.ts.map +1 -0
  32. package/dist/lib/release-install-policy.d.ts +31 -0
  33. package/dist/lib/release-install-policy.d.ts.map +1 -0
  34. package/dist/lib/transcriber.d.ts.map +1 -1
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +977 -194
  37. package/dist/sdk/index.js +5 -1
  38. package/dist/sdk/v1.generated.d.ts +4 -0
  39. package/dist/sdk/v1.generated.d.ts.map +1 -1
  40. package/dist/server/cloud-config.d.ts +9 -0
  41. package/dist/server/cloud-config.d.ts.map +1 -0
  42. package/dist/server/cloud-readiness.d.ts +10 -0
  43. package/dist/server/cloud-readiness.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +9 -18
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/index.js +1654 -304
  47. package/dist/server/migrate-command.d.ts +11 -0
  48. package/dist/server/migrate-command.d.ts.map +1 -0
  49. package/dist/server/openapi.d.ts +22 -0
  50. package/dist/server/openapi.d.ts.map +1 -1
  51. package/dist/server/repo.d.ts +8 -1
  52. package/dist/server/repo.d.ts.map +1 -1
  53. package/dist/server/serve.d.ts +6 -1
  54. package/dist/server/serve.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage.js +515 -80
  57. package/dist/store.d.ts +3 -1
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +10 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/package.json +7 -4
  63. package/packaging/macos/Empty.entitlements +5 -0
  64. package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
  65. package/packaging/macos/Verifier.entitlements +10 -0
  66. package/packaging/macos/artifact-verifier.sb +33 -0
  67. package/packaging/macos/build_release_pkg.sh +872 -0
  68. package/packaging/macos/managed_bootstrap.sh +623 -0
  69. package/packaging/macos/pkgutil_fingerprint.awk +37 -0
  70. package/packaging/macos/release_lifecycle.ts +463 -0
  71. package/packaging/macos/scripts/postinstall +565 -0
  72. package/packaging/macos/scripts/preinstall +297 -0
  73. package/scripts/build_companion_cli.sh +337 -0
  74. package/scripts/build_native_fs_guard.sh +59 -0
  75. package/scripts/generate-sdk.ts +19 -1
  76. package/scripts/install_macos_app.sh +1919 -98
  77. package/scripts/macos_artifact.ts +5316 -0
  78. package/scripts/migrate.ts +38 -10
  79. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  80. package/scripts/native/recordings_fs_guard.c +1167 -0
  81. package/scripts/native_fs_guard.ts +158 -0
  82. package/scripts/release-guard.ts +20 -1
  83. package/scripts/resolve_tailscale_cli.sh +389 -0
  84. package/scripts/smoke_macos_app.sh +573 -0
  85. package/src/native/Recordings/App/ContentView.swift +83 -0
  86. package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
  87. package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
  88. package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
  89. package/src/native/Recordings/App/RecordingsApp.swift +367 -44
  90. package/src/native/Recordings/App/RecordingsListView.swift +130 -0
  91. package/src/native/Recordings/App/RecordingsStore.swift +206 -0
  92. package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
  93. package/src/native/Recordings/App/SidebarView.swift +212 -0
  94. package/src/native/Recordings/App/Theme.swift +87 -0
  95. package/src/native/Recordings/Package.resolved +5 -5
  96. package/src/native/Recordings/Package.swift +62 -2
  97. package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
  98. package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
  99. package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
  101. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
  102. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
  103. package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
  104. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
  105. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
  106. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
  107. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
  108. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
  109. package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
  110. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
  111. package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
  112. package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
  113. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
  114. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
  115. package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
  116. package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
  117. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
  118. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
  119. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
  120. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
  121. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
  122. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
  123. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
  124. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
  125. package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
  126. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
  127. package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
  128. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
  129. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
  130. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
  131. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
  132. package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
  133. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
  134. package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
  135. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
  136. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
  137. package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
  138. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
  139. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
  140. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
  141. package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
  142. package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
  143. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
  144. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
  145. package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
  146. package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
  147. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
  148. package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
  149. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
  150. package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
  151. package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
  152. package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
  153. package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
  154. package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
  155. package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
  156. package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
  157. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
  158. package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
  159. package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
  160. package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
  161. package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
  162. package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
  163. package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
  164. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
  165. package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
  166. package/src/native/Recordings/build.sh +1973 -236
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -1,380 +0,0 @@
1
- import SwiftUI
2
- @preconcurrency import KeyboardShortcuts
3
-
4
- public struct MenuBarPopover: View {
5
- @ObservedObject public var engine: RecordingEngine
6
- @ObservedObject public var shortcuts: VoiceShortcuts
7
- @ObservedObject public var projectStore: ProjectStore
8
- @State private var copiedIndex: Int?
9
- @State private var filterProjectId: String?
10
-
11
- public init(engine: RecordingEngine, shortcuts: VoiceShortcuts, projectStore: ProjectStore) {
12
- self.engine = engine
13
- self.shortcuts = shortcuts
14
- self.projectStore = projectStore
15
- }
16
-
17
- private var filteredTranscriptions: [TranscriptionResult] {
18
- guard let filter = filterProjectId else {
19
- return engine.recentTranscriptions
20
- }
21
- if filter == "__none__" {
22
- return engine.recentTranscriptions.filter { $0.projectId == nil }
23
- }
24
- return engine.recentTranscriptions.filter { $0.projectId == filter }
25
- }
26
-
27
- public var body: some View {
28
- VStack(spacing: 0) {
29
- header
30
- .padding(.horizontal, 16).padding(.top, 12).padding(.bottom, 8)
31
- modePicker
32
- .padding(.horizontal, 16).padding(.top, 2)
33
- recordingArea
34
- .padding(.horizontal, 16).padding(.top, 10).padding(.bottom, 12)
35
- Divider()
36
- if !engine.recentTranscriptions.isEmpty {
37
- filterBar
38
- .padding(.horizontal, 16).padding(.vertical, 6)
39
- Divider()
40
- }
41
- recentList
42
- .frame(maxHeight: 200)
43
- Divider()
44
- footerMenu
45
- }
46
- .animation(.smooth(duration: 0.25), value: engine.isRecording)
47
- .animation(.smooth(duration: 0.25), value: engine.isTranscribing)
48
- }
49
-
50
- // MARK: - Header
51
-
52
- private var header: some View {
53
- VStack(alignment: .leading, spacing: 4) {
54
- HStack {
55
- HasnaLogoMark(size: 22)
56
- Text("Hasna Recordings")
57
- .font(.headline)
58
- Spacer()
59
- if let shortcut = KeyboardShortcuts.getShortcut(for: .toggleRecording) {
60
- Text(shortcut.description)
61
- .font(.caption)
62
- .foregroundStyle(.secondary)
63
- .padding(.horizontal, 7)
64
- .padding(.vertical, 3)
65
- .glassEffect(.regular, in: .capsule)
66
- }
67
- }
68
- if !projectStore.settings.projects.isEmpty {
69
- HStack(spacing: 4) {
70
- Image(systemName: "folder.fill")
71
- .foregroundStyle(projectStore.activeProject != nil ? AnyShapeStyle(.tint) : AnyShapeStyle(.secondary))
72
- Menu {
73
- Button("None") { projectStore.setActive(nil) }
74
- Divider()
75
- ForEach(projectStore.settings.projects) { project in
76
- Button {
77
- projectStore.setActive(project.id)
78
- } label: {
79
- HStack {
80
- Text(project.name)
81
- if project.id == projectStore.settings.activeProjectId {
82
- Image(systemName: "checkmark")
83
- }
84
- }
85
- }
86
- }
87
- } label: {
88
- Text(projectStore.activeProject?.name ?? "No project")
89
- .foregroundStyle(projectStore.activeProject == nil ? .secondary : .primary)
90
- }
91
- .menuStyle(.borderlessButton)
92
- .fixedSize()
93
- Spacer()
94
- }
95
- }
96
- }
97
- }
98
-
99
- // MARK: - Recording Area
100
-
101
- private var modePicker: some View {
102
- Picker("Mode", selection: $engine.mode) {
103
- ForEach(RecordingMode.allCases) { mode in
104
- Image(systemName: mode.icon)
105
- .tag(mode)
106
- .help(mode.rawValue)
107
- }
108
- }
109
- .pickerStyle(.segmented)
110
- .help(engine.mode.hint)
111
- }
112
-
113
- @ViewBuilder
114
- private var recordingArea: some View {
115
- if engine.isRecording {
116
- VStack(spacing: 8) {
117
- HStack {
118
- Image(systemName: "waveform")
119
- .symbolEffect(.variableColor.iterative, isActive: true)
120
- .foregroundStyle(.red)
121
- Text(fmt(engine.recordingDuration))
122
- .monospacedDigit()
123
- .contentTransition(.numericText())
124
- .font(.title3.weight(.medium))
125
- Spacer()
126
- Button("Stop", systemImage: "stop.fill") { engine.stopAndTranscribe() }
127
- .buttonStyle(.glassProminent)
128
- .tint(.red)
129
- .controlSize(.small)
130
- }
131
- if !engine.liveTranscriptionText.isEmpty {
132
- Text(engine.liveTranscriptionText)
133
- .font(.callout)
134
- .foregroundStyle(.primary)
135
- .frame(maxWidth: .infinity, alignment: .leading)
136
- .lineLimit(3)
137
- .contentTransition(.opacity)
138
- } else {
139
- Text("Listening…")
140
- .font(.caption)
141
- .foregroundStyle(.secondary)
142
- .frame(maxWidth: .infinity, alignment: .leading)
143
- }
144
- if let project = projectStore.activeProject {
145
- activeProjectTag(project)
146
- }
147
- }
148
- .padding(12)
149
- .glassEffect(.regular.tint(.red.opacity(0.1)), in: .rect(cornerRadius: 14))
150
- } else if engine.isTranscribing {
151
- VStack(spacing: 8) {
152
- HStack(spacing: 8) {
153
- ProgressView().controlSize(.small)
154
- Text("Transcribing…")
155
- .foregroundStyle(.secondary)
156
- Spacer()
157
- }
158
- if !engine.liveTranscriptionText.isEmpty {
159
- Text(engine.liveTranscriptionText)
160
- .font(.callout)
161
- .foregroundStyle(.primary)
162
- .frame(maxWidth: .infinity, alignment: .leading)
163
- .lineLimit(3)
164
- }
165
- if let project = projectStore.activeProject {
166
- activeProjectTag(project)
167
- }
168
- }
169
- .frame(maxWidth: .infinity)
170
- .padding(12)
171
- .glassEffect(.regular, in: .rect(cornerRadius: 14))
172
- } else {
173
- VStack(spacing: 8) {
174
- Button {
175
- engine.startRecording()
176
- } label: {
177
- Label("Record", systemImage: "mic.fill")
178
- .frame(maxWidth: .infinity)
179
- }
180
- .buttonStyle(.glassProminent)
181
- .controlSize(.large)
182
-
183
- if let shortcut = KeyboardShortcuts.getShortcut(for: .toggleRecording) {
184
- Text("or hold \(shortcut.description)")
185
- .font(.caption)
186
- .foregroundStyle(.tertiary)
187
- }
188
- Text(engine.statusMessage)
189
- .font(.caption)
190
- .foregroundStyle(engine.statusMessage == "Ready" ? Color.secondary : Color.orange)
191
- .multilineTextAlignment(.center)
192
- .lineLimit(2)
193
- }
194
- .frame(maxWidth: .infinity)
195
- .padding(12)
196
- .glassEffect(.regular, in: .rect(cornerRadius: 14))
197
- }
198
- }
199
-
200
- private func activeProjectTag(_ project: RecProject) -> some View {
201
- HStack(spacing: 4) {
202
- Image(systemName: "folder.fill").foregroundStyle(.tint)
203
- Text(project.name).foregroundStyle(.secondary)
204
- Spacer()
205
- }
206
- .font(.caption)
207
- }
208
-
209
- // MARK: - Filter Bar
210
-
211
- private var filterBar: some View {
212
- ScrollView(.horizontal, showsIndicators: false) {
213
- HStack(spacing: 6) {
214
- FilterChip(label: "All", isActive: filterProjectId == nil) {
215
- filterProjectId = nil
216
- }
217
- ForEach(projectStore.settings.projects) { project in
218
- let count = engine.recentTranscriptions.filter { $0.projectId == project.id }.count
219
- if count > 0 {
220
- FilterChip(
221
- label: "\(project.name) (\(count))",
222
- isActive: filterProjectId == project.id
223
- ) {
224
- filterProjectId = filterProjectId == project.id ? nil : project.id
225
- }
226
- }
227
- }
228
- let noProjectCount = engine.recentTranscriptions.filter { $0.projectId == nil }.count
229
- if noProjectCount > 0 {
230
- FilterChip(
231
- label: "No project (\(noProjectCount))",
232
- isActive: filterProjectId == "__none__"
233
- ) {
234
- filterProjectId = filterProjectId == "__none__" ? nil : "__none__"
235
- }
236
- }
237
- }
238
- }
239
- }
240
-
241
- // MARK: - Recent
242
-
243
- private var recentList: some View {
244
- Group {
245
- if filteredTranscriptions.isEmpty {
246
- Spacer()
247
- } else {
248
- ScrollView {
249
- LazyVStack(alignment: .leading, spacing: 2) {
250
- ForEach(filteredTranscriptions.indices, id: \.self) { i in
251
- let item = filteredTranscriptions[i]
252
- TranscriptionRow(
253
- item: item,
254
- showProject: filterProjectId == nil,
255
- isCopied: copiedIndex == i
256
- ) {
257
- withAnimation(.easeInOut(duration: 0.15)) {
258
- copiedIndex = i
259
- }
260
- engine.pasteIntoFrontApp(item.displayText)
261
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
262
- withAnimation { if copiedIndex == i { copiedIndex = nil } }
263
- }
264
- }
265
- }
266
- }
267
- .padding(.horizontal, 16).padding(.vertical, 6)
268
- }
269
- }
270
- }
271
- }
272
-
273
- // MARK: - Footer
274
-
275
- private var footerMenu: some View {
276
- VStack(spacing: 0) {
277
- SettingsLink {
278
- HStack {
279
- Text("Settings...")
280
- Spacer()
281
- Text("⌘,").foregroundStyle(.tertiary)
282
- }
283
- .contentShape(Rectangle())
284
- .padding(.horizontal, 16).padding(.vertical, 6)
285
- }
286
- .buttonStyle(.plain)
287
-
288
- Divider()
289
-
290
- Button {
291
- NSApplication.shared.terminate(nil)
292
- } label: {
293
- HStack {
294
- Text("Quit Hasna Recordings")
295
- Spacer()
296
- Text("⌘Q").foregroundStyle(.tertiary)
297
- }
298
- .contentShape(Rectangle())
299
- .padding(.horizontal, 16).padding(.vertical, 6)
300
- }
301
- .buttonStyle(.plain)
302
- }
303
- }
304
-
305
- private func fmt(_ t: TimeInterval) -> String {
306
- String(format: "%d:%02d", Int(t) / 60, Int(t) % 60)
307
- }
308
- }
309
-
310
- // MARK: - Filter Chip
311
-
312
- struct FilterChip: View {
313
- let label: String
314
- let isActive: Bool
315
- let action: () -> Void
316
-
317
- var body: some View {
318
- Button(action: action) {
319
- Text(label)
320
- .font(.caption)
321
- .padding(.horizontal, 9)
322
- .padding(.vertical, 4)
323
- }
324
- .buttonStyle(.plain)
325
- .foregroundStyle(isActive ? .primary : .secondary)
326
- .glassEffect(
327
- isActive ? .regular.tint(.accentColor.opacity(0.25)).interactive() : .regular.interactive(),
328
- in: .capsule
329
- )
330
- }
331
- }
332
-
333
- // MARK: - Transcription Row
334
-
335
- struct TranscriptionRow: View {
336
- let item: TranscriptionResult
337
- let showProject: Bool
338
- let isCopied: Bool
339
- let onCopy: () -> Void
340
-
341
- var body: some View {
342
- VStack(alignment: .leading, spacing: 2) {
343
- HStack(alignment: .top, spacing: 8) {
344
- Text(item.displayText)
345
- .lineLimit(2)
346
- .frame(maxWidth: .infinity, alignment: .leading)
347
-
348
- if isCopied {
349
- Image(systemName: "checkmark")
350
- .foregroundStyle(.green)
351
- .transition(.scale.combined(with: .opacity))
352
- } else {
353
- Text(relativeTime(item.timestamp))
354
- .foregroundStyle(.tertiary)
355
- }
356
- }
357
- if showProject, let name = item.projectName {
358
- HStack(spacing: 3) {
359
- Image(systemName: "folder")
360
- Text(name)
361
- }
362
- .foregroundStyle(.tertiary)
363
- }
364
- }
365
- .padding(.vertical, 4)
366
- .padding(.horizontal, 6)
367
- .contentShape(Rectangle())
368
- .onTapGesture { onCopy() }
369
- .onHover { hovering in
370
- if hovering { NSCursor.pointingHand.push() } else { NSCursor.pop() }
371
- }
372
- }
373
-
374
- private func relativeTime(_ date: Date) -> String {
375
- let s = Date().timeIntervalSince(date)
376
- if s < 60 { return "now" }
377
- if s < 3600 { return "\(Int(s / 60))m" }
378
- return "\(Int(s / 3600))h"
379
- }
380
- }