Thank you. I should have mentioned that I also use the dataSource "requestStart" event, and if dataSource.hasChanges() reports yes, I prompt the user to save or not. Regardless of the user choosing OK or cancel, I let the new request continue, but if they choose OK, then I call dataSource.sync(). This lets the changes get saved and then the new request complete (like paging/sorting/filtering on server-side).
When using this technique, calling e.preventDefault() in the grid's save event *does* work and let the screen and model be updated, but when the new requestStart happens, it does not detect any "dirty" rows.
By using the dataSource's change event and checking e.action === "itemchange" && e.field === my field, and using model.set() there, then grid detects a change when the new request starts.