Repository navigation
feat: wire editor to REST load, autosave, reload persistence - #289
ViewFromTheBox merged 2 commits into
Conversation
Boots the React editor from the package's REST endpoints so opening
/editor/{id} loads the post via GET, edits debounce a PUT autosave,
and reloads pick up persisted changes. Adds a direct Zustand store
subscription in useAutosave so debounce/save cycles survive every
isDirty transition, not just the first one.
Includes Vitest coverage for the REST client, autosave happy/error/
retry paths, and the EditorBoot load + retry flow.
Closes #275
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR wires the visual editor to REST: adds EditorBoot for load lifecycle, a post REST client, a debounced/exponential-backoff Changes
Sequence DiagramssequenceDiagram
participant Browser as Browser (mount)
participant EditorBoot as EditorBoot
participant REST as REST API
participant Store as EditorStore
participant Ready as EditorBootReady
Browser->>EditorBoot: mount(postId, apiBase)
EditorBoot->>EditorBoot: set state = loading
EditorBoot->>REST: GET /posts/{postId}
alt success
REST-->>EditorBoot: PostPayload (blocks)
EditorBoot->>Store: createEditorStore(blocks)
EditorBoot->>EditorBoot: set state = ready
EditorBoot->>Ready: render with store
else failure
REST-->>EditorBoot: error / non-2xx
EditorBoot->>EditorBoot: set state = error
EditorBoot->>Browser: render error UI (retry)
Browser->>EditorBoot: click retry
EditorBoot->>REST: repeat GET /posts/{postId}
end
sequenceDiagram
participant User as User
participant Store as EditorStore
participant Autosave as useAutosave
participant Timer as Debounce Timer
participant REST as REST API
participant Shell as EditorShell
User->>Store: edit -> store.isDirty = true
Store->>Autosave: notify dirty
Autosave->>Timer: schedule save (debounceMs)
Timer-->>Autosave: timeout fires
Autosave->>Autosave: abort prev request (if any)
Autosave->>Store: snapshot blocks
Autosave->>Shell: status = saving
Autosave->>REST: PUT /posts/{postId} {blocks}
alt save success
REST-->>Autosave: PostPayload
Autosave->>Store: markClean() if snapshot matches
Autosave->>Shell: status = saved
else save failure
REST-->>Autosave: error
Autosave->>Shell: status = error, retryCount++
Autosave->>Timer: schedule retry (baseMs * 2^retryCount)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
resources/js/visual-editor/editor/components/editor.css (1)
415-428: Add explicit keyboard focus styling for the retry button.The retry control has hover styling but no explicit
:focus-visibletreatment. Adding one makes keyboard focus reliably visible.♿ Suggested CSS patch
.ve-editor-boot__retry:hover { background: `#333`; } + +.ve-editor-boot__retry:focus-visible { + outline: 2px solid `#0a84ff`; + outline-offset: 2px; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@resources/js/visual-editor/editor/components/editor.css` around lines 415 - 428, The retry button (.ve-editor-boot__retry) lacks keyboard focus styling; add an explicit :focus-visible rule to make keyboard focus visible (e.g., outline or box-shadow and ensure outline-offset and background/ border contrasts work with existing styles). Update the CSS to include a .ve-editor-boot__retry:focus-visible selector that applies a clear focus indicator consistent with the hover state, and ensure it does not rely solely on :hover so keyboard users see the focus.resources/js/visual-editor/editor/components/EditorBoot.tsx (2)
127-141: Consider adding a dismiss mechanism for the error toast.The toast persists as long as
status === 'error'. Users may want to dismiss it manually, especially since the next successful save will clear it automatically. This is optional and can be addressed in a follow-up.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@resources/js/visual-editor/editor/components/EditorBoot.tsx` around lines 127 - 141, The error toast rendered by AutosaveAnnouncer shows when state.status === 'error' and never lets users dismiss it; update AutosaveAnnouncer to accept an onDismiss callback prop (or implement an internal local dismissed flag) and render a dismiss control (e.g., a button with an accessible aria-label) inside the div that calls that onDismiss to clear or hide the toast, and wire the parent component that manages AutosaveState to clear state.lastError or propagate the dismiss action; reference AutosaveAnnouncer, AutosaveState, state.status and state.lastError when making the changes.
11-15: UnusedpostTypeprop.
postTypeis defined inEditorBootPropsbut never used in the component. Either remove it from the interface or wire it through to the REST client if the API requires it.🔧 Option 1: Remove unused prop
export interface EditorBootProps { postId: string; - postType: string; apiBase: string; }Also applies to: 22-22
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@resources/js/visual-editor/editor/components/EditorBoot.tsx` around lines 11 - 15, The EditorBootProps interface declares a postType prop that is never used in the EditorBoot component; either remove postType from EditorBootProps to avoid dead props or forward it into the code path that needs it (e.g., pass postType into the REST client or initialization call used by EditorBoot). Locate the EditorBootProps interface and the EditorBoot component and implement one of the two fixes: delete the postType property from the interface and remove any unused prop destructuring, or propagate postType into the REST client/init function (the call where postId and apiBase are used) so the API receives postType as part of the request payload or client configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@resources/js/visual-editor/editor/components/EditorShell.tsx`:
- Around line 45-47: The displayed status label (computed by
resolveStatusLabel(saveStatus, isDirty)) can differ from the data token
(statusToken) when saveStatus.status === 'idle'; update the logic that sets
statusToken (near statusToken, resolveStatusLabel, saveStatus, isDirty) so that
if saveStatus?.status is 'idle' you fall back to the same dirty/saved token used
for the label (i.e., compute statusToken as saveStatus?.status unless it equals
'idle', in which case use isDirty ? 'dirty' : 'saved'); apply the same change to
the other occurrences noted (around lines with similar
statusToken/resolveStatusLabel usage) so data-ve-save-status always matches the
visible label.
---
Nitpick comments:
In `@resources/js/visual-editor/editor/components/editor.css`:
- Around line 415-428: The retry button (.ve-editor-boot__retry) lacks keyboard
focus styling; add an explicit :focus-visible rule to make keyboard focus
visible (e.g., outline or box-shadow and ensure outline-offset and background/
border contrasts work with existing styles). Update the CSS to include a
.ve-editor-boot__retry:focus-visible selector that applies a clear focus
indicator consistent with the hover state, and ensure it does not rely solely on
:hover so keyboard users see the focus.
In `@resources/js/visual-editor/editor/components/EditorBoot.tsx`:
- Around line 127-141: The error toast rendered by AutosaveAnnouncer shows when
state.status === 'error' and never lets users dismiss it; update
AutosaveAnnouncer to accept an onDismiss callback prop (or implement an internal
local dismissed flag) and render a dismiss control (e.g., a button with an
accessible aria-label) inside the div that calls that onDismiss to clear or hide
the toast, and wire the parent component that manages AutosaveState to clear
state.lastError or propagate the dismiss action; reference AutosaveAnnouncer,
AutosaveState, state.status and state.lastError when making the changes.
- Around line 11-15: The EditorBootProps interface declares a postType prop that
is never used in the EditorBoot component; either remove postType from
EditorBootProps to avoid dead props or forward it into the code path that needs
it (e.g., pass postType into the REST client or initialization call used by
EditorBoot). Locate the EditorBootProps interface and the EditorBoot component
and implement one of the two fixes: delete the postType property from the
interface and remove any unused prop destructuring, or propagate postType into
the REST client/init function (the call where postId and apiBase are used) so
the API receives postType as part of the request payload or client
configuration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ab098332-6686-464f-b9b5-e2767431ec59
📒 Files selected for processing (11)
resources/js/visual-editor/editor/components/EditorBoot.tsxresources/js/visual-editor/editor/components/EditorShell.tsxresources/js/visual-editor/editor/components/__tests__/EditorBoot.test.tsxresources/js/visual-editor/editor/components/editor.cssresources/js/visual-editor/editor/components/index.tsresources/js/visual-editor/editor/main.tsxresources/js/visual-editor/editor/rest/__tests__/postRestClient.test.tsresources/js/visual-editor/editor/rest/__tests__/useAutosave.test.tsxresources/js/visual-editor/editor/rest/index.tsresources/js/visual-editor/editor/rest/postRestClient.tsresources/js/visual-editor/editor/rest/useAutosave.ts
…s idle When saveStatus.status === 'idle', the label falls back to the dirty/saved text but statusToken was still reporting 'idle', so data-ve-save-status could diverge from what the user sees. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Description
Boots the React editor from the package's REST endpoints so opening
/editor/{id}loads the post viaGET, edits debounce aPUTautosave, and reloads pick up persisted changes. This is the Phase 1 deliverable from #237.Closes: #275
Type of Change
Related Issue
Issue: #275
Motivation and Context
Phase 1 needs an end-to-end wiring: boot editor → load via REST → edit → autosave → reload → verify persistence. This PR is the last deliverable piece on top of the REST endpoints from #274 and the Zustand store from #265.
Changes Made
editor/rest/postRestClient.ts— typedfetchPost()/savePost()withPostRestError, CSRF pickup from<meta name="csrf-token">, andsame-origincredentials.editor/rest/useAutosave.ts— subscribes directly to the Zustand store (not viauseEffectdeps) so debounce/save cycles survive everyisDirtytransition, not just the first. 1.5s debounce,AbortControllercancellation, exponential backoff retry (max 3), dirty flag preserved on failure, snapshot-equality check to avoid clobbering in-flight edits.editor/components/EditorBoot.tsx— handles loading/error/ready states, builds the store from the loaded payload, mounts autosave, shows an inline retry button on load failure and a toast on save failure.EditorShellstatusbar now showsSaving…/Saved/Save failedwhen asaveStatusprop is provided; legacy dirty-only mode preserved for existing tests.main.tsxreadsdata-post-id/data-post-type/data-api-baseand mounts<EditorBoot>.editor.css— basic heading styles forh1–h6inside.ve-block-heading, plus boot/loading/error/toast styles.How Has This Been Tested?
Tests Performed:
postRestClient.test.ts(7 tests) — URL joining, header shape, CSRF pickup, success, non-2xx, shape validation.useAutosave.test.tsx(5 tests) — Debounce happy path, no-op when clean, error + dirty preservation, exponential backoff retry chain, and a regression test asserting three consecutive edits trigger three saves.EditorBoot.test.tsx(2 tests) — Load happy path, error state + retry flow./packages/visual-editor/editor/{id}in the dev app → edited block content →Saving…→Savedappeared in the statusbar → reload showed persisted changes. Verified repeated edits trigger fresh save cycles (not just the first one).Test details: All 238 vitest tests pass. TypeScript typecheck clean.
Accessibility Tests Run
<button>, focusable.role="status" aria-live="polite"; error state usesrole="alert".Tests Added
Documentation
useAutosaveexplaining the subscribe-based design.Pre-Submission Checklist
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Style
Tests