A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Forward undo/redo to the draft and publish flow

useStateWithHistory's undo/redo swap BlockEditorProvider's controlled
value, but the provider doesn't re-fire onInput/onChange for a
controlled-value change, so the localStorage draft and Studio's publish
state silently missed every undo/redo. Typing, undoing, then publishing
would publish the pre-undo content.

Route undo/redo through wrapped handlers that flag a pending forward; an
effect on value then forwards the restored tree exactly once. Also fix
two comments left stale by the IBE migration (iso.blocks.allowBlocks and
onLoad no longer exist).

+30 -7
+28 -5
src/components/SkyEditor.tsx
··· 1 1 import { 2 2 useCallback, 3 + useEffect, 3 4 useMemo, 4 5 useRef, 5 6 useState, ··· 116 117 const [ status, setStatus ] = useState< string >( '' ); 117 118 const [ showInspector, setShowInspector ] = useState( false ); 118 119 const inspectorToggleRef = useRef< HTMLButtonElement >( null ); 120 + // Set by undo/redo so the value-sync effect below forwards the restored tree. 121 + const pendingForward = useRef( false ); 119 122 120 123 // Persist a local draft and forward the live tree to the publish flow. 121 124 const forward = useCallback( ··· 145 148 [ setBlocks, forward ] 146 149 ); 147 150 151 + // `undo`/`redo` swap the controlled `value`, but `BlockEditorProvider` doesn't 152 + // re-fire `onInput`/`onChange` for a controlled-value change (it would loop) — 153 + // so the draft + publish flow would otherwise miss it. Flag the change here and 154 + // forward the restored tree once `value` reflects it. 155 + const handleUndo = useCallback( () => { 156 + pendingForward.current = true; 157 + undo(); 158 + }, [ undo ] ); 159 + const handleRedo = useCallback( () => { 160 + pendingForward.current = true; 161 + redo(); 162 + }, [ redo ] ); 163 + useEffect( () => { 164 + if ( ! pendingForward.current ) { 165 + return; 166 + } 167 + pendingForward.current = false; 168 + forward( value as BlockInstance[] ); 169 + }, [ value, forward ] ); 170 + 148 171 const settings = useMemo( 149 172 () => ( { 150 173 // A custom mediaUpload routes uploads to the PDS as a blob (SP3). ··· 171 194 } 172 195 event.preventDefault(); 173 196 if ( event.shiftKey ) { 174 - redo(); 197 + handleRedo(); 175 198 } else { 176 - undo(); 199 + handleUndo(); 177 200 } 178 201 }, 179 - [ undo, redo ] 202 + [ handleUndo, handleRedo ] 180 203 ); 181 204 182 205 return ( ··· 200 223 <Button 201 224 icon={ undoIcon } 202 225 label="Undo" 203 - onClick={ undo } 226 + onClick={ handleUndo } 204 227 disabled={ ! hasUndo } 205 228 /> 206 229 <Button 207 230 icon={ redoIcon } 208 231 label="Redo" 209 - onClick={ redo } 232 + onClick={ handleRedo } 210 233 disabled={ ! hasRedo } 211 234 /> 212 235 <Button
+1 -1
src/components/Studio.tsx
··· 137 137 138 138 if ( status === 'signed-in' && agent && did ) { 139 139 // Re-mount the editor when switching article (or after a new publish) so the 140 - // SkyEditor canvas resets via onLoad/initialBlocks. The title is Studio-owned 140 + // SkyEditor canvas resets via initialBlocks (read at mount). The title is Studio-owned 141 141 // state now, so it doesn't reset on remount — the title/blocks reset for a new 142 142 // publish happens in PublishPanel's `onComplete` below. 143 143 const editorKey = editing ? `edit-${ editing.rkey }` : `new-${ refreshKey }`;
+1 -1
src/lib/blocks/allowlist.ts
··· 2 2 * The curated SkyPress block set — the content model (Decision 0002). 3 3 * 4 4 * Used two ways: 5 - * - the editor restricts insertion to these via `iso.blocks.allowBlocks`; 5 + * - the editor restricts insertion to these via `settings.allowedBlockTypes`; 6 6 * - the render/serialize path registers only these (plus fallbacks). 7 7 * 8 8 * `core/list-item` is included because `core/list` nests it; it is structural