@agent-native/core 0.114.5 → 0.114.7
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +6 -4
- package/corpus/core/src/cli/mcp.ts +124 -0
- package/corpus/core/src/cli/skills-content/help.ts +4 -3
- package/corpus/core/src/cli/skills-content/index.ts +1 -0
- package/corpus/core/src/cli/skills-content/rewind-skill.ts +52 -0
- package/corpus/core/src/cli/skills.ts +115 -13
- package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
- package/corpus/core/src/mcp/screen-memory-stdio.ts +1360 -104
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
- package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
- package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
- package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
- package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
- package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
- package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
- package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
- package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
- package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +1 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
- package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
- package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
- package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
- package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
- package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
- package/corpus/templates/clips/desktop/src/app.tsx +2478 -223
- package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
- package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
- package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
- package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
- package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
- package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
- package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
- package/corpus/templates/clips/desktop/src/styles.css +956 -134
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
- package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
- package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
- package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
- package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
- package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
- package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
- package/corpus/templates/clips/shared/meeting-join-url.ts +0 -5
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
- package/corpus/templates/content/AGENTS.md +4 -1
- package/corpus/templates/content/actions/_content-spaces.ts +6 -1
- package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
- package/corpus/templates/content/actions/_database-utils.ts +83 -15
- package/corpus/templates/content/actions/add-database-item.ts +3 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
- package/corpus/templates/content/actions/delete-content-database.ts +12 -9
- package/corpus/templates/content/actions/delete-database-items.ts +3 -2
- package/corpus/templates/content/actions/delete-document.ts +233 -6
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +4 -1
- package/corpus/templates/content/actions/list-content-databases.ts +1 -0
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
- package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
- package/corpus/templates/content/actions/pull-document.ts +15 -2
- package/corpus/templates/content/actions/restore-content-database.ts +48 -11
- package/corpus/templates/content/actions/restore-document.ts +28 -0
- package/corpus/templates/content/actions/search-documents.ts +2 -1
- package/corpus/templates/content/actions/view-screen.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
- package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
- package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
- package/corpus/templates/content/app/i18n-data.ts +83 -2
- package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
- package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +3 -0
- package/corpus/templates/content/server/db/schema.ts +2 -0
- package/corpus/templates/content/server/lib/public-documents.ts +8 -2
- package/corpus/templates/content/server/plugins/db.ts +52 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
- package/corpus/templates/content/shared/api.ts +10 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/ui/progress.tsx +3 -2
- package/dist/cli/mcp-config-writers.d.ts +2 -2
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +6 -6
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +79 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills-content/help.d.ts +1 -1
- package/dist/cli/skills-content/help.d.ts.map +1 -1
- package/dist/cli/skills-content/help.js +4 -3
- package/dist/cli/skills-content/help.js.map +1 -1
- package/dist/cli/skills-content/index.d.ts +1 -0
- package/dist/cli/skills-content/index.d.ts.map +1 -1
- package/dist/cli/skills-content/index.js +1 -0
- package/dist/cli/skills-content/index.js.map +1 -1
- package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
- package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/rewind-skill.js +53 -0
- package/dist/cli/skills-content/rewind-skill.js.map +1 -0
- package/dist/cli/skills.d.ts +6 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +90 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +3 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +4 -4
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +55 -6
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +365 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp/screen-memory-stdio.js +1014 -91
- package/dist/mcp/screen-memory-stdio.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +90 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
- package/dist/mcp-client/screen-memory-local.js +590 -12
- package/dist/mcp-client/screen-memory-local.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
- package/src/cli/mcp-config-writers.ts +6 -4
- package/src/cli/mcp.ts +124 -0
- package/src/cli/skills-content/help.ts +4 -3
- package/src/cli/skills-content/index.ts +1 -0
- package/src/cli/skills-content/rewind-skill.ts +52 -0
- package/src/cli/skills.ts +115 -13
- package/src/client/resources/ResourceTree.tsx +11 -5
- package/src/client/resources/ResourcesPanel.tsx +141 -18
- package/src/mcp/screen-memory-stdio.ts +1360 -104
- package/src/mcp-client/index.ts +9 -0
- package/src/mcp-client/screen-memory-local.ts +847 -13
|
@@ -15,10 +15,10 @@ const POPOVER_DEFAULT_HEIGHT_LOGICAL: f64 = 520.0;
|
|
|
15
15
|
//
|
|
16
16
|
// Clips-owned recording chrome (toolbar / countdown / finalizing /
|
|
17
17
|
// recording-pill) gets `NSWindow.sharingType = NSWindowSharingNone` so it does
|
|
18
|
-
// not leak into the recorded video. The main popover
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// not leak into the recorded video. The main popover follows the same user
|
|
19
|
+
// preference: private by default, shareable only when "Show Clips in screen
|
|
20
|
+
// captures" is enabled. Reopening it during a recording must not silently
|
|
21
|
+
// override that choice.
|
|
22
22
|
// Recording-time exclusion has two effects on macOS: screen pickers don't list
|
|
23
23
|
// excluded windows, and full-screen captures omit them from the compositor
|
|
24
24
|
// output. This is the same mechanism Loom, 1Password, and CleanShot use to keep
|
|
@@ -183,12 +183,9 @@ pub fn set_capture_included(_window: &WebviewWindow) {
|
|
|
183
183
|
/// immediately on anything currently on screen. Called from
|
|
184
184
|
/// `set_feature_config` when the toggle flips.
|
|
185
185
|
///
|
|
186
|
-
/// The popover
|
|
187
|
-
///
|
|
188
|
-
/// parked
|
|
189
|
-
/// the toggle, so the next `show_popover` / `park_popover_offscreen` call
|
|
190
|
-
/// picks up the new setting. Rewriting it here would clobber the parked
|
|
191
|
-
/// state mid-recording.
|
|
186
|
+
/// The popover follows the same preference as ordinary Clips chrome. Applying
|
|
187
|
+
/// it here makes the toggle take effect immediately whether the popover is
|
|
188
|
+
/// visible or parked as the 2x2 recording controller.
|
|
192
189
|
///
|
|
193
190
|
/// Region-guide overlays are private recorder aids, not Clips chrome demos, so
|
|
194
191
|
/// they stay excluded even when the debug toggle makes the rest visible.
|
|
@@ -198,9 +195,6 @@ pub fn reapply_capture_exclusion_to_overlays(app: &tauri::AppHandle) {
|
|
|
198
195
|
let visible = crate::config::show_in_screen_capture(app);
|
|
199
196
|
let windows = app.webview_windows();
|
|
200
197
|
for (label, window) in &windows {
|
|
201
|
-
if label.as_str() == "popover" {
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
198
|
// The meeting reminder is a notification, not Clips recording
|
|
205
199
|
// chrome — keep it visible in captures regardless of the debug
|
|
206
200
|
// toggle so it never gets re-excluded on a config change.
|
|
@@ -16,6 +16,36 @@
|
|
|
16
16
|
//!
|
|
17
17
|
use tauri::AppHandle;
|
|
18
18
|
|
|
19
|
+
/// One timestamped segment produced by bounded, offline-file transcription.
|
|
20
|
+
/// Timestamps are relative to the supplied audio, never to wall-clock time.
|
|
21
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
22
|
+
pub(crate) struct OfflineTranscriptSegment {
|
|
23
|
+
pub start_ms: i64,
|
|
24
|
+
pub end_ms: i64,
|
|
25
|
+
pub text: String,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Transcribe already-decoded 16 kHz mono samples with the process-wide
|
|
29
|
+
/// Whisper context. This is the reusable file-transcription entry point: the
|
|
30
|
+
/// caller owns container decoding and the samples are never persisted here.
|
|
31
|
+
///
|
|
32
|
+
/// Blocking work -- call from a dedicated worker thread, not the async runtime.
|
|
33
|
+
pub(crate) fn transcribe_offline_file_samples(
|
|
34
|
+
app: &AppHandle,
|
|
35
|
+
samples: &[f32],
|
|
36
|
+
language: Option<&str>,
|
|
37
|
+
) -> Result<Vec<OfflineTranscriptSegment>, String> {
|
|
38
|
+
#[cfg(target_os = "macos")]
|
|
39
|
+
{
|
|
40
|
+
macos::transcribe_offline_file_samples(app, samples, language)
|
|
41
|
+
}
|
|
42
|
+
#[cfg(not(target_os = "macos"))]
|
|
43
|
+
{
|
|
44
|
+
let _ = (app, samples, language);
|
|
45
|
+
Err("local Whisper file transcription is supported on macOS only".to_owned())
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
#[tauri::command]
|
|
20
50
|
pub async fn whisper_transcription_start(
|
|
21
51
|
app: AppHandle,
|
|
@@ -112,6 +142,9 @@ mod macos {
|
|
|
112
142
|
use tauri::{AppHandle, Emitter};
|
|
113
143
|
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
|
|
114
144
|
|
|
145
|
+
use crate::capture_audio_bus::{
|
|
146
|
+
try_subscribe, AudioSources, AudioSubscription, SubscriptionAttempt,
|
|
147
|
+
};
|
|
115
148
|
use crate::native_speech::macos::{
|
|
116
149
|
start_raw_mic_capture, MicVoiceProcessingMode, RawMicCapture,
|
|
117
150
|
};
|
|
@@ -121,6 +154,8 @@ mod macos {
|
|
|
121
154
|
};
|
|
122
155
|
use crate::whisper_model::{ensure_model, model_file};
|
|
123
156
|
|
|
157
|
+
const MEETING_AUDIO_OWNER: &str = "meeting-whisper";
|
|
158
|
+
|
|
124
159
|
/// One transcript segment with real timestamps from whisper, already
|
|
125
160
|
/// offset onto the meeting timeline (ms since capture start).
|
|
126
161
|
#[derive(Serialize, Clone)]
|
|
@@ -185,6 +220,31 @@ mod macos {
|
|
|
185
220
|
Ok(())
|
|
186
221
|
}
|
|
187
222
|
|
|
223
|
+
pub(super) fn transcribe_offline_file_samples(
|
|
224
|
+
app: &AppHandle,
|
|
225
|
+
samples: &[f32],
|
|
226
|
+
language: Option<&str>,
|
|
227
|
+
) -> Result<Vec<super::OfflineTranscriptSegment>, String> {
|
|
228
|
+
const CHUNK_SAMPLES: usize = 25 * 16_000;
|
|
229
|
+
|
|
230
|
+
let ctx = context(app)?;
|
|
231
|
+
let mut state = ctx
|
|
232
|
+
.create_state()
|
|
233
|
+
.map_err(|e| format!("whisper state init failed: {e}"))?;
|
|
234
|
+
let mut segments = Vec::new();
|
|
235
|
+
for (chunk_index, chunk) in samples.chunks(CHUNK_SAMPLES).enumerate() {
|
|
236
|
+
let chunk_offset_ms = chunk_index as i64 * 25_000;
|
|
237
|
+
segments.extend(infer(&mut state, chunk, language).into_iter().map(
|
|
238
|
+
|(start_ms, end_ms, text)| super::OfflineTranscriptSegment {
|
|
239
|
+
start_ms: chunk_offset_ms.saturating_add(start_ms),
|
|
240
|
+
end_ms: chunk_offset_ms.saturating_add(end_ms),
|
|
241
|
+
text,
|
|
242
|
+
},
|
|
243
|
+
));
|
|
244
|
+
}
|
|
245
|
+
Ok(segments)
|
|
246
|
+
}
|
|
247
|
+
|
|
188
248
|
// ---- resampling -------------------------------------------------------
|
|
189
249
|
|
|
190
250
|
/// One linearly-interpolated 16 kHz output sample at output index `i`,
|
|
@@ -383,6 +443,12 @@ mod macos {
|
|
|
383
443
|
/// deliberately deferred to the worker so we never allocate/compute on
|
|
384
444
|
/// the realtime audio thread.
|
|
385
445
|
fn push(&self, frames: &[f32]) {
|
|
446
|
+
// A bus publish may already have cloned this callback when the
|
|
447
|
+
// subscription is dropped. Refuse that final in-flight delivery
|
|
448
|
+
// once teardown has signalled the worker to stop.
|
|
449
|
+
if !self.running.load(Ordering::SeqCst) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
386
452
|
if let Ok(mut buf) = self.buf.lock() {
|
|
387
453
|
buf.extend_from_slice(frames);
|
|
388
454
|
}
|
|
@@ -826,18 +892,31 @@ mod macos {
|
|
|
826
892
|
}
|
|
827
893
|
|
|
828
894
|
struct Session {
|
|
895
|
+
app: AppHandle,
|
|
829
896
|
// macOS 15+ meetings use a combined SCK capture, so there is no
|
|
830
897
|
// competing AVAudioEngine / VoiceProcessingIO mic input to stop.
|
|
831
898
|
mic_cap: Option<RawMicCapture>,
|
|
832
899
|
// System capture is optional — skipped when the user turns system
|
|
833
900
|
// audio off, so neither the recording nor the transcript include it.
|
|
834
901
|
sys_cap: Option<RawSckAudioCapture>,
|
|
902
|
+
// When Rewind already owns the requested SCK audio sources, meeting
|
|
903
|
+
// Whisper receives fan-out PCM through this subscription and opens no
|
|
904
|
+
// physical mic/system recorder of its own.
|
|
905
|
+
_shared_audio: Option<AudioSubscription>,
|
|
906
|
+
temporary_audio: Option<crate::screen_memory::TemporaryAudioLease>,
|
|
835
907
|
mic: Arc<WhisperStream>,
|
|
836
908
|
sys: Option<Arc<WhisperStream>>,
|
|
837
909
|
/// Who started this session — see `SessionOwner`.
|
|
838
910
|
owner: SessionOwner,
|
|
839
911
|
}
|
|
840
912
|
|
|
913
|
+
impl Drop for Session {
|
|
914
|
+
fn drop(&mut self) {
|
|
915
|
+
drop(self._shared_audio.take());
|
|
916
|
+
release_temporary_audio(&self.app, self.temporary_audio.take());
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
841
920
|
// SAFETY: the capture handles hold refcounted ObjC objects (already
|
|
842
921
|
// `Send`); the streams are `Arc` over `Send + Sync` interiors. We only move
|
|
843
922
|
// the session through the `Mutex`, never alias across threads.
|
|
@@ -936,10 +1015,80 @@ mod macos {
|
|
|
936
1015
|
as Arc<dyn Fn(&[f32]) + Send + Sync>
|
|
937
1016
|
});
|
|
938
1017
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1018
|
+
// If Rewind is running visuals-only, upgrade that one physical
|
|
1019
|
+
// producer before touching the bus. Off/paused Rewind returns no lease
|
|
1020
|
+
// and preserves the legacy meeting capture path.
|
|
1021
|
+
let temporary_audio = if owner == SessionOwner::Meeting {
|
|
1022
|
+
match crate::screen_memory::acquire_temporary_audio_consumer(
|
|
1023
|
+
&app,
|
|
1024
|
+
MEETING_AUDIO_OWNER,
|
|
1025
|
+
crate::capture_graph::CaptureConsumer::Meeting,
|
|
1026
|
+
true,
|
|
1027
|
+
capture_system,
|
|
1028
|
+
) {
|
|
1029
|
+
Ok(lease) => lease,
|
|
1030
|
+
Err(error) => {
|
|
1031
|
+
mic_stream.stop();
|
|
1032
|
+
if let Some(sys_stream) = &sys_stream {
|
|
1033
|
+
sys_stream.stop();
|
|
1034
|
+
}
|
|
1035
|
+
return Err(error);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
None
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// Make the shared-vs-physical choice atomically inside the bus. If a
|
|
1043
|
+
// Rewind producer exists but lacks a requested source, `try_subscribe`
|
|
1044
|
+
// returns a typed error and we fail closed: no combined SCK fallback,
|
|
1045
|
+
// RawMicCapture, or raw system capture is opened.
|
|
1046
|
+
let shared_audio = if owner == SessionOwner::Meeting {
|
|
1047
|
+
let mic_for_shared = mic_stream.clone();
|
|
1048
|
+
let shared_mic = Arc::new(move |samples: &[f32], sample_rate: f64| {
|
|
1049
|
+
mic_for_shared.set_src_rate(sample_rate);
|
|
1050
|
+
mic_for_shared.push(samples);
|
|
1051
|
+
});
|
|
1052
|
+
let shared_system = sys_stream.as_ref().map(|stream| {
|
|
1053
|
+
let stream = stream.clone();
|
|
1054
|
+
Arc::new(move |samples: &[f32], sample_rate: f64| {
|
|
1055
|
+
stream.set_src_rate(sample_rate);
|
|
1056
|
+
stream.push(samples);
|
|
1057
|
+
}) as crate::capture_audio_bus::AudioCallback
|
|
1058
|
+
});
|
|
1059
|
+
match try_subscribe(
|
|
1060
|
+
AudioSources::new(true, capture_system),
|
|
1061
|
+
Some(shared_mic),
|
|
1062
|
+
shared_system,
|
|
1063
|
+
) {
|
|
1064
|
+
Ok(SubscriptionAttempt::Subscribed(subscription)) => Some(subscription),
|
|
1065
|
+
Ok(SubscriptionAttempt::NoProducer) => {
|
|
1066
|
+
if temporary_audio.is_some() {
|
|
1067
|
+
release_temporary_audio(&app, temporary_audio);
|
|
1068
|
+
mic_stream.stop();
|
|
1069
|
+
if let Some(sys_stream) = &sys_stream {
|
|
1070
|
+
sys_stream.stop();
|
|
1071
|
+
}
|
|
1072
|
+
return Err("shared-audio-producer-unavailable-after-upgrade".into());
|
|
1073
|
+
}
|
|
1074
|
+
None
|
|
1075
|
+
}
|
|
1076
|
+
Err(error) => {
|
|
1077
|
+
release_temporary_audio(&app, temporary_audio);
|
|
1078
|
+
mic_stream.stop();
|
|
1079
|
+
if let Some(sys_stream) = &sys_stream {
|
|
1080
|
+
sys_stream.stop();
|
|
1081
|
+
}
|
|
1082
|
+
return Err(error.to_string());
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
} else {
|
|
1086
|
+
None
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
let combined_cap = if shared_audio.is_none()
|
|
1090
|
+
&& should_use_combined_sck_capture(owner, supports_sck_microphone_capture())
|
|
1091
|
+
{
|
|
943
1092
|
match start_raw_meeting_capture(
|
|
944
1093
|
app.clone(),
|
|
945
1094
|
mic_device_id.clone(),
|
|
@@ -963,7 +1112,10 @@ mod macos {
|
|
|
963
1112
|
None
|
|
964
1113
|
};
|
|
965
1114
|
|
|
966
|
-
let (mic_cap, sys_cap) = if
|
|
1115
|
+
let (mic_cap, sys_cap) = if shared_audio.is_some() {
|
|
1116
|
+
eprintln!("[whisper] using shared Rewind microphone/system PCM producer");
|
|
1117
|
+
(None, None)
|
|
1118
|
+
} else if let Some(combined_cap) = combined_cap {
|
|
967
1119
|
// Both SCK outputs are configured at 48 kHz.
|
|
968
1120
|
mic_stream.set_src_rate(48000.0);
|
|
969
1121
|
(None, Some(combined_cap))
|
|
@@ -1004,10 +1156,30 @@ mod macos {
|
|
|
1004
1156
|
(Some(mic_cap), sys_cap)
|
|
1005
1157
|
};
|
|
1006
1158
|
|
|
1007
|
-
let mut slot = session_slot().lock()
|
|
1159
|
+
let mut slot = match session_slot().lock() {
|
|
1160
|
+
Ok(slot) => slot,
|
|
1161
|
+
Err(error) => {
|
|
1162
|
+
drop(shared_audio);
|
|
1163
|
+
release_temporary_audio(&app, temporary_audio);
|
|
1164
|
+
mic_stream.stop();
|
|
1165
|
+
if let Some(sys_stream) = &sys_stream {
|
|
1166
|
+
sys_stream.stop();
|
|
1167
|
+
}
|
|
1168
|
+
if let Some(mic_cap) = mic_cap {
|
|
1169
|
+
mic_cap.stop();
|
|
1170
|
+
}
|
|
1171
|
+
if let Some(sys_cap) = sys_cap {
|
|
1172
|
+
sys_cap.stop();
|
|
1173
|
+
}
|
|
1174
|
+
return Err(error.to_string());
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1008
1177
|
*slot = Some(Session {
|
|
1178
|
+
app: app.clone(),
|
|
1009
1179
|
mic_cap,
|
|
1010
1180
|
sys_cap,
|
|
1181
|
+
_shared_audio: shared_audio,
|
|
1182
|
+
temporary_audio,
|
|
1011
1183
|
mic: mic_stream,
|
|
1012
1184
|
sys: sys_stream,
|
|
1013
1185
|
owner,
|
|
@@ -1039,12 +1211,23 @@ mod macos {
|
|
|
1039
1211
|
eprintln!("[whisper] transcription timeline reset");
|
|
1040
1212
|
}
|
|
1041
1213
|
|
|
1042
|
-
|
|
1214
|
+
fn release_temporary_audio(
|
|
1215
|
+
app: &AppHandle,
|
|
1216
|
+
lease: Option<crate::screen_memory::TemporaryAudioLease>,
|
|
1217
|
+
) {
|
|
1218
|
+
if let Some(lease) = lease {
|
|
1219
|
+
if let Err(error) = crate::screen_memory::release_temporary_audio_consumer(app, lease) {
|
|
1220
|
+
eprintln!("[whisper] Rewind audio lease release failed: {error}");
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
pub fn stop(app: &AppHandle) {
|
|
1043
1226
|
let session = match session_slot().lock() {
|
|
1044
1227
|
Ok(mut slot) => slot.take(),
|
|
1045
|
-
Err(
|
|
1228
|
+
Err(poisoned) => poisoned.into_inner().take(),
|
|
1046
1229
|
};
|
|
1047
|
-
let Some(session) = session else {
|
|
1230
|
+
let Some(mut session) = session else {
|
|
1048
1231
|
return;
|
|
1049
1232
|
};
|
|
1050
1233
|
// Signal workers to stop. They flush a final transcript after the loop.
|
|
@@ -1053,12 +1236,16 @@ mod macos {
|
|
|
1053
1236
|
sys.stop();
|
|
1054
1237
|
}
|
|
1055
1238
|
// Stop captures so no more samples arrive while workers flush.
|
|
1056
|
-
if let Some(mic_cap) = session.mic_cap {
|
|
1239
|
+
if let Some(mic_cap) = session.mic_cap.take() {
|
|
1057
1240
|
mic_cap.stop();
|
|
1058
1241
|
}
|
|
1059
|
-
if let Some(sys_cap) = session.sys_cap {
|
|
1242
|
+
if let Some(sys_cap) = session.sys_cap.take() {
|
|
1060
1243
|
sys_cap.stop();
|
|
1061
1244
|
}
|
|
1245
|
+
// Unsubscribe before a last-consumer release rotates the Rewind
|
|
1246
|
+
// producer back to its persisted visuals-only mode.
|
|
1247
|
+
drop(session._shared_audio.take());
|
|
1248
|
+
release_temporary_audio(app, session.temporary_audio.take());
|
|
1062
1249
|
// Wait up to 4 s for both workers to finish their final flush so
|
|
1063
1250
|
// trailing speech is not lost when the frontend unregisters listeners.
|
|
1064
1251
|
let deadline = Instant::now() + Duration::from_secs(4);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.tauri.app/config/2",
|
|
3
|
+
"productName": "Clips Alpha",
|
|
4
|
+
"identifier": "com.clips.tray.alpha",
|
|
5
|
+
"bundle": {
|
|
6
|
+
"createUpdaterArtifacts": false,
|
|
7
|
+
"targets": ["app"],
|
|
8
|
+
"macOS": {
|
|
9
|
+
"minimumSystemVersion": "26.0"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"plugins": {
|
|
13
|
+
"updater": {
|
|
14
|
+
"active": false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -5,11 +5,6 @@ function isZoomMeetingHost(hostname: string): boolean {
|
|
|
5
5
|
return hostname === "zoom.us" || hostname.endsWith(".zoom.us");
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Prefer Zoom's native URI when an HTTPS calendar link carries a meeting id.
|
|
10
|
-
* Other providers and URL shapes retain their original URL as the safe
|
|
11
|
-
* fallback.
|
|
12
|
-
*/
|
|
13
8
|
export function resolveNativeMeetingJoinUrl(joinUrl: string): string {
|
|
14
9
|
try {
|
|
15
10
|
const url = new URL(joinUrl);
|
|
@@ -82,12 +82,20 @@ pnpm action update-document --id abc123 --description "Stable guidance for what
|
|
|
82
82
|
|
|
83
83
|
### delete-document
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Move a document and all its children to Trash. IDs, bodies, hierarchy, and
|
|
86
|
+
database membership remain intact so the subtree can be restored.
|
|
86
87
|
|
|
87
88
|
```bash
|
|
88
89
|
pnpm action delete-document --id abc123
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
Restore the root subtree, or permanently delete it only after it is in Trash:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pnpm action restore-document --id abc123
|
|
96
|
+
pnpm action permanently-delete-document --id abc123
|
|
97
|
+
```
|
|
98
|
+
|
|
91
99
|
## Comments
|
|
92
100
|
|
|
93
101
|
Comments are Notion/Google-Docs-style **inline comments**. Selecting text and commenting leaves the passage **highlighted inline** via a ProseMirror decoration overlay — nothing is written into the markdown body, so the document round-trips unchanged. Each thread stores the quoted text plus surrounding context (`anchorPrefix`/`anchorSuffix`) and an approximate `anchorStartOffset`, so the highlight follows the text as the document is edited, disambiguates repeated text, and degrades gracefully (the thread stays in the sidebar) when its text is deleted.
|
|
@@ -146,6 +146,9 @@ cd templates/content && pnpm action <name> [args]
|
|
|
146
146
|
| `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
|
|
147
147
|
| `list-trashed-content-databases` | | List soft-deleted databases visible in the sidebar Trash surface |
|
|
148
148
|
| `restore-content-database` | `--databaseId <id>` | Restore a soft-deleted database from the sidebar Trash surface |
|
|
149
|
+
| `list-trashed-documents` | | List access-filtered page roots visible in the sidebar Trash surface |
|
|
150
|
+
| `restore-document` | `--id <id>` | Restore a page and the subtree moved to Trash with it |
|
|
151
|
+
| `permanently-delete-document` | `--id <id>` | Permanently destroy a document subtree already in Trash |
|
|
149
152
|
| `get-content-database-source` | `--databaseId <id>` or `--documentId <id>` | Inspect local/no-source or source-backed status, mappings, row identity, freshness, and change sets |
|
|
150
153
|
| `attach-content-database-source` | `--databaseId <id>` or `--documentId <id> [--sourceType mock-local\|builder-cms\|local-table\|notion-database] [--sourceName] [--sourceTable] [--relationshipMode items\|details] [--join <json>]` | Attach a source binding; use `items` to add more Builder rows and `details` to match a read-only local/Notion source onto existing rows |
|
|
151
154
|
| `list-notion-database-sources` | `[--query <text>] [--limit <1-100>]` | List Notion data sources visible through the current user's OAuth connection; returns IDs/names only, never tokens |
|
|
@@ -175,7 +178,7 @@ cd templates/content && pnpm action <name> [args]
|
|
|
175
178
|
| `reorder-document-property` | `--documentId <id> --propertyId <propertyId> --targetPropertyId <id> [--position before\|after]` | Reorder a property definition within its database (used to reorder Blocks fields on the page) |
|
|
176
179
|
| `set-document-discoverability` | `--id <id> --hideFromSearch true\|false [--includeChildren true\|false]` | Hide/show an org-accessible document in Organization/search while keeping link access |
|
|
177
180
|
| `move-document` | `--id <id> [--parentId] [--position]` | Move or reorder a document in the page tree |
|
|
178
|
-
| `delete-document` | `--id <id>` |
|
|
181
|
+
| `delete-document` | `--id <id>` | Move a document and its recursive children to Trash |
|
|
179
182
|
|
|
180
183
|
Database views follow Notion-style tab labels. When creating or duplicating
|
|
181
184
|
views in `viewConfig`, use unique default names (`Table 2`, `SEO copy 2`, etc.)
|
|
@@ -806,7 +806,11 @@ async function provisionOwnedContentSpace(
|
|
|
806
806
|
export async function provisionSourceBackedContentSpace(
|
|
807
807
|
db: Db,
|
|
808
808
|
userEmail: string,
|
|
809
|
-
input: {
|
|
809
|
+
input: {
|
|
810
|
+
connectionId: string;
|
|
811
|
+
name: string;
|
|
812
|
+
propertyValues?: Record<string, unknown>;
|
|
813
|
+
},
|
|
810
814
|
) {
|
|
811
815
|
const connectionId = input.connectionId.trim();
|
|
812
816
|
if (!connectionId) throw new Error("Local folder connection ID is required");
|
|
@@ -814,6 +818,7 @@ export async function provisionSourceBackedContentSpace(
|
|
|
814
818
|
spaceId: sourceBackedContentSpaceId(userEmail, connectionId),
|
|
815
819
|
name: input.name.trim() || "Local folder",
|
|
816
820
|
kind: "source_backed",
|
|
821
|
+
propertyValues: input.propertyValues,
|
|
817
822
|
});
|
|
818
823
|
}
|
|
819
824
|
|
|
@@ -151,6 +151,7 @@ export async function resolveDatabaseRowsForBatch(
|
|
|
151
151
|
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
152
152
|
rowPredicates.length === 1 ? rowPredicates[0] : or(...rowPredicates),
|
|
153
153
|
isNull(schema.contentDatabases.deletedAt),
|
|
154
|
+
isNull(schema.documents.trashedAt),
|
|
154
155
|
),
|
|
155
156
|
)
|
|
156
157
|
.orderBy(asc(schema.contentDatabaseItems.position));
|
|
@@ -185,9 +186,21 @@ export async function renumberDatabaseRows(
|
|
|
185
186
|
now: string,
|
|
186
187
|
) {
|
|
187
188
|
const rows = await db
|
|
188
|
-
.select(
|
|
189
|
+
.select({
|
|
190
|
+
id: schema.contentDatabaseItems.id,
|
|
191
|
+
documentId: schema.contentDatabaseItems.documentId,
|
|
192
|
+
})
|
|
189
193
|
.from(schema.contentDatabaseItems)
|
|
190
|
-
.
|
|
194
|
+
.innerJoin(
|
|
195
|
+
schema.documents,
|
|
196
|
+
eq(schema.documents.id, schema.contentDatabaseItems.documentId),
|
|
197
|
+
)
|
|
198
|
+
.where(
|
|
199
|
+
and(
|
|
200
|
+
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
201
|
+
isNull(schema.documents.trashedAt),
|
|
202
|
+
),
|
|
203
|
+
)
|
|
191
204
|
.orderBy(asc(schema.contentDatabaseItems.position));
|
|
192
205
|
if (rows.length === 0) return;
|
|
193
206
|
|
|
@@ -22,7 +22,10 @@ import type {
|
|
|
22
22
|
ContentDatabaseResponse,
|
|
23
23
|
} from "../shared/api.js";
|
|
24
24
|
import { favoriteDocumentIds } from "./_content-favorites.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
listContentOrganizationMemberships,
|
|
27
|
+
normalizeContentSpaceEmail,
|
|
28
|
+
} from "./_content-space-access.js";
|
|
26
29
|
import { getAllContentDatabaseSourceSnapshots } from "./_database-source-utils.js";
|
|
27
30
|
import {
|
|
28
31
|
applyFederatedOverlayValues,
|
|
@@ -81,6 +84,8 @@ export const contentDatabaseListDocumentSelection = {
|
|
|
81
84
|
sourcePath: schema.documents.sourcePath,
|
|
82
85
|
sourceRootPath: schema.documents.sourceRootPath,
|
|
83
86
|
sourceUpdatedAt: schema.documents.sourceUpdatedAt,
|
|
87
|
+
trashedAt: schema.documents.trashedAt,
|
|
88
|
+
trashRootId: schema.documents.trashRootId,
|
|
84
89
|
visibility: schema.documents.visibility,
|
|
85
90
|
ownerEmail: schema.documents.ownerEmail,
|
|
86
91
|
orgId: schema.documents.orgId,
|
|
@@ -300,9 +305,14 @@ export async function getContentDatabaseResponse(
|
|
|
300
305
|
|
|
301
306
|
const { limit, offset } = normalizeContentDatabasePageOptions(options);
|
|
302
307
|
const userEmail = getRequestUserEmail();
|
|
308
|
+
const normalizedUserEmail = userEmail
|
|
309
|
+
? normalizeContentSpaceEmail(userEmail)
|
|
310
|
+
: null;
|
|
303
311
|
const activeOrgId = getRequestOrgId();
|
|
304
312
|
const authorizedOrgIds =
|
|
305
|
-
database.systemRole === "favorites"
|
|
313
|
+
(database.systemRole === "favorites" ||
|
|
314
|
+
database.systemRole === "workspaces") &&
|
|
315
|
+
userEmail
|
|
306
316
|
? [
|
|
307
317
|
...new Set([
|
|
308
318
|
...(await listContentOrganizationMemberships(userEmail)).map(
|
|
@@ -312,6 +322,45 @@ export async function getContentDatabaseResponse(
|
|
|
312
322
|
]),
|
|
313
323
|
]
|
|
314
324
|
: [];
|
|
325
|
+
const workspacesVisibleDocumentIds =
|
|
326
|
+
database.systemRole === "workspaces" && normalizedUserEmail
|
|
327
|
+
? (
|
|
328
|
+
await db
|
|
329
|
+
.select({
|
|
330
|
+
documentId: schema.contentSpaceCatalogItems.documentId,
|
|
331
|
+
ownerEmail: schema.contentSpaces.ownerEmail,
|
|
332
|
+
orgId: schema.contentSpaces.orgId,
|
|
333
|
+
})
|
|
334
|
+
.from(schema.contentSpaceCatalogItems)
|
|
335
|
+
.innerJoin(
|
|
336
|
+
schema.contentSpaces,
|
|
337
|
+
eq(
|
|
338
|
+
schema.contentSpaces.id,
|
|
339
|
+
schema.contentSpaceCatalogItems.spaceId,
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
.where(
|
|
343
|
+
and(
|
|
344
|
+
eq(
|
|
345
|
+
schema.contentSpaceCatalogItems.catalogDatabaseId,
|
|
346
|
+
databaseId,
|
|
347
|
+
),
|
|
348
|
+
eq(
|
|
349
|
+
schema.contentSpaceCatalogItems.ownerEmail,
|
|
350
|
+
normalizedUserEmail,
|
|
351
|
+
),
|
|
352
|
+
isNull(schema.contentSpaces.archivedAt),
|
|
353
|
+
),
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
.filter(
|
|
357
|
+
(row) =>
|
|
358
|
+
normalizeContentSpaceEmail(row.ownerEmail) ===
|
|
359
|
+
normalizedUserEmail ||
|
|
360
|
+
(!!row.orgId && authorizedOrgIds.includes(row.orgId)),
|
|
361
|
+
)
|
|
362
|
+
.map((row) => row.documentId)
|
|
363
|
+
: null;
|
|
315
364
|
const favoritesVisibleDocumentIds =
|
|
316
365
|
database.systemRole === "favorites" && userEmail
|
|
317
366
|
? (
|
|
@@ -331,6 +380,7 @@ export async function getContentDatabaseResponse(
|
|
|
331
380
|
}),
|
|
332
381
|
),
|
|
333
382
|
),
|
|
383
|
+
isNull(schema.documents.trashedAt),
|
|
334
384
|
documentDiscoveryFilter({
|
|
335
385
|
userEmail,
|
|
336
386
|
orgIds: authorizedOrgIds,
|
|
@@ -351,6 +401,11 @@ export async function getContentDatabaseResponse(
|
|
|
351
401
|
: undefined;
|
|
352
402
|
const visibleItemFilter = and(
|
|
353
403
|
eq(schema.contentDatabaseItems.databaseId, databaseId),
|
|
404
|
+
sql`exists (
|
|
405
|
+
select 1 from ${schema.documents}
|
|
406
|
+
where ${schema.documents.id} = ${schema.contentDatabaseItems.documentId}
|
|
407
|
+
and ${schema.documents.trashedAt} is null
|
|
408
|
+
)`,
|
|
354
409
|
organizationFilesItemFilter,
|
|
355
410
|
favoritesVisibleDocumentIds
|
|
356
411
|
? favoritesVisibleDocumentIds.length > 0
|
|
@@ -360,6 +415,14 @@ export async function getContentDatabaseResponse(
|
|
|
360
415
|
)
|
|
361
416
|
: sql`1 = 0`
|
|
362
417
|
: undefined,
|
|
418
|
+
workspacesVisibleDocumentIds
|
|
419
|
+
? workspacesVisibleDocumentIds.length > 0
|
|
420
|
+
? inArray(
|
|
421
|
+
schema.contentDatabaseItems.documentId,
|
|
422
|
+
workspacesVisibleDocumentIds,
|
|
423
|
+
)
|
|
424
|
+
: sql`1 = 0`
|
|
425
|
+
: undefined,
|
|
363
426
|
);
|
|
364
427
|
const [itemCount] = await db
|
|
365
428
|
.select({ count: sql<number>`COUNT(*)` })
|
|
@@ -388,23 +451,28 @@ export async function getContentDatabaseResponse(
|
|
|
388
451
|
schema.documents.id,
|
|
389
452
|
items.map((item) => item.documentId),
|
|
390
453
|
),
|
|
454
|
+
isNull(schema.documents.trashedAt),
|
|
391
455
|
database.systemRole === "favorites"
|
|
392
456
|
? favoritesVisibleDocumentIds?.length
|
|
393
457
|
? inArray(schema.documents.id, favoritesVisibleDocumentIds)
|
|
394
458
|
: sql`1 = 0`
|
|
395
|
-
: database.systemRole === "
|
|
396
|
-
?
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
459
|
+
: database.systemRole === "workspaces"
|
|
460
|
+
? workspacesVisibleDocumentIds?.length
|
|
461
|
+
? inArray(schema.documents.id, workspacesVisibleDocumentIds)
|
|
462
|
+
: sql`1 = 0`
|
|
463
|
+
: database.systemRole === "files" && database.orgId
|
|
464
|
+
? and(
|
|
465
|
+
eq(schema.documents.orgId, database.orgId),
|
|
466
|
+
or(
|
|
467
|
+
eq(schema.documents.visibility, "org"),
|
|
468
|
+
eq(schema.documents.visibility, "public"),
|
|
469
|
+
),
|
|
470
|
+
or(
|
|
471
|
+
eq(schema.documents.hideFromSearch, 0),
|
|
472
|
+
isNull(schema.documents.hideFromSearch),
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
: eq(schema.documents.ownerEmail, database.ownerEmail),
|
|
408
476
|
),
|
|
409
477
|
)
|
|
410
478
|
: [];
|
|
@@ -41,6 +41,9 @@ export default defineAction({
|
|
|
41
41
|
),
|
|
42
42
|
);
|
|
43
43
|
if (!database) throw new Error(`Database "${databaseId}" not found`);
|
|
44
|
+
if (database.systemRole === "workspaces") {
|
|
45
|
+
throw new Error("Use create-content-space to add a workspace");
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
const access = await assertAccess(
|
|
46
49
|
"document",
|
|
@@ -51,6 +51,7 @@ export default defineAction({
|
|
|
51
51
|
.optional()
|
|
52
52
|
.default(false)
|
|
53
53
|
.describe("Create a separate private Content space for this folder"),
|
|
54
|
+
propertyValues: z.record(z.string(), z.unknown()).optional(),
|
|
54
55
|
truthPolicy: truthPolicySchema.describe(
|
|
55
56
|
"Whether Content, the folder, or reviewed conflict resolution is authoritative",
|
|
56
57
|
),
|
|
@@ -130,7 +131,7 @@ export default defineAction({
|
|
|
130
131
|
const provisioned = await provisionSourceBackedContentSpace(
|
|
131
132
|
db,
|
|
132
133
|
userEmail,
|
|
133
|
-
{ connectionId, name: label },
|
|
134
|
+
{ connectionId, name: label, propertyValues: args.propertyValues },
|
|
134
135
|
);
|
|
135
136
|
targetSpaceId = provisioned.spaceId;
|
|
136
137
|
targetDatabaseId = provisioned.filesDatabaseId;
|