Skip to content

Commit a64d94d

Browse files
cscheidclaude
andcommitted
Merge bd-ov4gqk3m: q2 preview --allow-edit persists React-preview edits to disk
Browser block edits in q2 preview now write back to the source .qmd, opt-in via --allow-edit. Without the flag the preview is fully read-only: no edit affordance renders and the hub runs with DiskWritePolicy::ReadOnly so document changes from any client can never modify the user's files. Plan: claude-notes/plans/2026-06-10-q2-preview-edit-writeback.md Follow-ups: bd-g4uw7d8g (eager sync trigger), bd-f8d753iq (automated e2e) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 parents 35b9007 + c2065ea commit a64d94d

33 files changed

Lines changed: 1516 additions & 105 deletions

claude-notes/plans/2026-06-10-q2-preview-edit-writeback.md

Lines changed: 376 additions & 0 deletions
Large diffs are not rendered by default.

crates/quarto-hub/src/context.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ use crate::index::{IndexDocument, load_or_create_index};
2222
use crate::peer::spawn_peer_connection;
2323
use crate::resource::{create_binary_document, detect_mime_type};
2424
use crate::storage::StorageManager;
25-
use crate::sync::{SyncAllResult, SyncResult, sync_all_documents, sync_file_by_path};
25+
use crate::sync::{
26+
DiskWritePolicy, SyncAllResult, SyncResult, sync_all_documents, sync_file_by_path,
27+
};
2628
use crate::sync_state::SyncState;
2729
use crate::watch::WatchFilter;
2830

@@ -97,6 +99,14 @@ pub struct HubConfig {
9799
/// works at `/ws`, which hub-client and the q2-preview SPA both
98100
/// already use as their canonical connect path.
99101
pub register_root_ws: bool,
102+
103+
/// Whether automerge document changes may be written back to files on
104+
/// disk. See [`DiskWritePolicy`].
105+
///
106+
/// Default: `WriteBack` (the hub's collaborative semantics). `q2 preview`
107+
/// sets `ReadOnly` unless `--allow-edit` is given, so browser-originated
108+
/// document changes can never modify the user's files.
109+
pub disk_write_policy: DiskWritePolicy,
100110
}
101111

102112
impl Default for HubConfig {
@@ -114,6 +124,7 @@ impl Default for HubConfig {
114124
auth_config: None,
115125
allow_insecure_auth: false,
116126
register_root_ws: true,
127+
disk_write_policy: DiskWritePolicy::default(),
117128
}
118129
}
119130
}
@@ -165,6 +176,10 @@ pub struct HubContext {
165176
/// the originating `HubConfig`.
166177
register_root_ws: bool,
167178

179+
/// See [`HubConfig::disk_write_policy`]. Applied to every filesystem
180+
/// sync this context performs (initial, periodic, watcher-triggered).
181+
disk_write_policy: DiskWritePolicy,
182+
168183
/// Maps peer IDs to authenticated user emails.
169184
/// Populated by handle_websocket when auth is enabled; read by the
170185
/// AuditAccessPolicy for audit logging.
@@ -266,8 +281,14 @@ impl HubContext {
266281
let mut sync_state = SyncState::load(storage.hub_dir())?;
267282

268283
// Perform initial sync on startup
269-
let sync_result =
270-
sync_all_documents(&repo, &index, project_root, &mut sync_state).await;
284+
let sync_result = sync_all_documents(
285+
&repo,
286+
&index,
287+
project_root,
288+
&mut sync_state,
289+
config.disk_write_policy,
290+
)
291+
.await;
271292

272293
info!(
273294
synced = sync_result.total_synced(),
@@ -290,6 +311,7 @@ impl HubContext {
290311
let auth_config = config.auth_config.take();
291312
let allow_insecure_auth = config.allow_insecure_auth;
292313
let register_root_ws = config.register_root_ws;
314+
let disk_write_policy = config.disk_write_policy;
293315

294316
Ok(Self {
295317
storage,
@@ -302,6 +324,7 @@ impl HubContext {
302324
auth_state: OnceLock::new(),
303325
allow_insecure_auth,
304326
register_root_ws,
327+
disk_write_policy,
305328
peer_emails,
306329
})
307330
}
@@ -353,7 +376,14 @@ impl HubContext {
353376
return SyncAllResult::default();
354377
};
355378
let mut sync_state = sync_state_mutex.lock().await;
356-
sync_all_documents(&self.repo, &self.index, project_root, &mut sync_state).await
379+
sync_all_documents(
380+
&self.repo,
381+
&self.index,
382+
project_root,
383+
&mut sync_state,
384+
self.disk_write_policy,
385+
)
386+
.await
357387
}
358388

359389
/// Sync a single file by its path.
@@ -382,6 +412,7 @@ impl HubContext {
382412
file_path,
383413
&project_root,
384414
&mut sync_state,
415+
self.disk_write_policy,
385416
)
386417
.await
387418
}

crates/quarto-hub/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ async fn main() -> anyhow::Result<()> {
224224
auth_config,
225225
allow_insecure_auth: args.allow_insecure_auth,
226226
register_root_ws: true,
227+
// The collaborative hub always persists document changes to disk.
228+
disk_write_policy: quarto_hub::sync::DiskWritePolicy::WriteBack,
227229
};
228230

229231
server::run_server(storage, config).await?;

0 commit comments

Comments
 (0)