Skip to content

feat: wire editor to REST load, autosave, reload persistence - #289

Merged
ViewFromTheBox merged 2 commits into
add/phase-onefrom
enhancement/275-wire-editor-rest-load-save-persistence
Apr 14, 2026
Merged

ViewFromTheBox merged 2 commits into
add/phase-onefrom
enhancement/275-wire-editor-rest-load-save-persistence

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Apr 14, 2026 •

Copy link
Copy Markdown
Member

Description

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. This is the Phase 1 deliverable from #237.

Closes: #275

Type of Change

  • New feature (adds new functionality)

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

  • New editor/rest/postRestClient.ts — typed fetchPost() / savePost() with PostRestError, CSRF pickup from <meta name="csrf-token">, and same-origin credentials.
  • New editor/rest/useAutosave.ts — subscribes directly to the Zustand store (not via useEffect deps) so debounce/save cycles survive every isDirty transition, not just the first. 1.5s debounce, AbortController cancellation, exponential backoff retry (max 3), dirty flag preserved on failure, snapshot-equality check to avoid clobbering in-flight edits.
  • New 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.
  • EditorShell statusbar now shows Saving… / Saved / Save failed when a saveStatus prop is provided; legacy dirty-only mode preserved for existing tests.
  • main.tsx reads data-post-id / data-post-type / data-api-base and mounts <EditorBoot>.
  • editor.css — basic heading styles for h1–h6 inside .ve-block-heading, plus boot/loading/error/toast styles.

How Has This Been Tested?

Tests Performed:

  1. postRestClient.test.ts (7 tests) — URL joining, header shape, CSRF pickup, success, non-2xx, shape validation.
  2. 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.
  3. EditorBoot.test.tsx (2 tests) — Load happy path, error state + retry flow.
  4. Manual acceptance: opened /packages/visual-editor/editor/{id} in the dev app → edited block content → Saving… → Saved appeared 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

  • Keyboard navigation tested — retry button is a native <button>, focusable.
  • Screen reader tested
  • Color contrast verified — loading/error states use high-contrast tokens.
  • ARIA labels checked — loading state uses role="status" aria-live="polite"; error state uses role="alert".

Tests Added

  • Unit tests added/updated
  • Integration tests added/updated
  • All tests passing

Documentation

  • Inline code documentation added — block comments on useAutosave explaining the subscribe-based design.

Pre-Submission Checklist

  • Code passes all tests (238/238)
  • Code follows project style guide
  • Self-review completed
  • No new warnings generated

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • App now boots the visual editor with explicit loading/error/ready lifecycle.
    • Implemented debounced autosave with exponential backoff retries and clear save-status updates.
    • Save status shown as “Saving…”, “Saved”, or “Save failed”; autosave errors surface via a toast/announcer and retry affordance.
  • Bug Fixes

    • Improved error handling and retry behavior for failed post loads and saves.
  • Style

    • Added editor heading and boot UI styling.
  • Tests

    • Added end-to-end and unit tests covering boot, autosave, and REST behavior.

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>
@coderabbitai

coderabbitai Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8bf1de4d-7ffe-46dd-a2ca-60ee80cc99fd

📥 Commits

Reviewing files that changed from the base of the PR and between fb55f4d and c102ea5.

📒 Files selected for processing (1)
  • resources/js/visual-editor/editor/components/EditorShell.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/js/visual-editor/editor/components/EditorShell.tsx

📝 Walkthrough

Walkthrough

The PR wires the visual editor to REST: adds EditorBoot for load lifecycle, a post REST client, a debounced/exponential-backoff useAutosave hook for PUT saves, updates EditorShell to surface save status, adds CSS for boot UI, and expands test coverage for load/save/autosave behaviors.

Changes

Cohort / File(s) Summary
Editor Boot & entry
resources/js/visual-editor/editor/components/EditorBoot.tsx, resources/js/visual-editor/editor/main.tsx, resources/js/visual-editor/editor/components/index.ts
Adds EditorBoot component that GETs post payload, manages loading/error/ready states, creates editor store, and delegates to ready UI; swaps app entry to mount EditorBoot; re-exports EditorBoot/props.
REST client
resources/js/visual-editor/editor/rest/postRestClient.ts, resources/js/visual-editor/editor/rest/index.ts
New fetchPost/savePost APIs, PostRestError, payload/options types, header/CSRF handling, JSON parsing/validation, and re-exports via rest/index.ts.
Autosave hook
resources/js/visual-editor/editor/rest/useAutosave.ts
New useAutosave hook implementing debounce, abortable saves, snapshotting, exponential-backoff retries, state (idle
Editor UI & status
resources/js/visual-editor/editor/components/EditorShell.tsx, resources/js/visual-editor/editor/components/editor.css
EditorShell gains optional saveStatus prop propagated to Statusbar; status label logic and data-ve-save-status attribute added. New CSS for editor boot UI and block heading styles.
Tests
resources/js/visual-editor/editor/components/__tests__/EditorBoot.test.tsx, resources/js/visual-editor/editor/rest/__tests__/postRestClient.test.ts, resources/js/visual-editor/editor/rest/__tests__/useAutosave.test.tsx
New Vitest suites covering boot lifecycle (load/error/retry), REST client request/response/error cases, and autosave debounce/retry/dirty-flag behavior with fake timers and fetch mocks.

Sequence Diagrams

sequenceDiagram
    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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 I hopped in to fetch and save with care,

Blocks snug in JSON, no data to spare.
Debounced little hops, retries on the trail,
"Saving…" then "Saved" — a carrot-fueled tale.
✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: wire editor to REST load, autosave, reload persistence' accurately describes the main objective of the PR—wiring the editor to REST endpoints for loading, autosaving, and persistence on reload.
Linked Issues check ✅ Passed All primary objectives from #275 are implemented: load via REST with loading/error states [#275], debounced autosave with save indicators [#275], error handling with retry buttons and non-blocking toasts [#275], and comprehensive test coverage [#275].
Out of Scope Changes check ✅ Passed All changes align with the #275 Phase 1 scope: REST integration, autosave, error handling, and testing. No explicit save button, conflict resolution, or offline editing were added, as correctly marked out of scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhancement/275-wire-editor-rest-load-save-persistence

Comment @coderabbitai help to get the list of available commands and usage tips.

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Apr 14, 2026
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review April 14, 2026 20:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-visible treatment. 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: Unused postType prop.

postType is defined in EditorBootProps but 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

📥 Commits

Reviewing files that changed from the base of the PR and between 626c5b3 and fb55f4d.

📒 Files selected for processing (11)
  • resources/js/visual-editor/editor/components/EditorBoot.tsx
  • resources/js/visual-editor/editor/components/EditorShell.tsx
  • resources/js/visual-editor/editor/components/__tests__/EditorBoot.test.tsx
  • resources/js/visual-editor/editor/components/editor.css
  • resources/js/visual-editor/editor/components/index.ts
  • resources/js/visual-editor/editor/main.tsx
  • resources/js/visual-editor/editor/rest/__tests__/postRestClient.test.ts
  • resources/js/visual-editor/editor/rest/__tests__/useAutosave.test.tsx
  • resources/js/visual-editor/editor/rest/index.ts
  • resources/js/visual-editor/editor/rest/postRestClient.ts
  • resources/js/visual-editor/editor/rest/useAutosave.ts

Comment thread resources/js/visual-editor/editor/components/EditorShell.tsx
…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>
@ViewFromTheBox
ViewFromTheBox merged commit 5574051 into add/phase-one Apr 14, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Apr 14, 2026
@ViewFromTheBox
ViewFromTheBox deleted the enhancement/275-wire-editor-rest-load-save-persistence branch April 14, 2026 21:08
@coderabbitai coderabbitai Bot mentioned this pull request Apr 19, 2026
17 of 21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Phase 1.13: Wire editor to REST — load, save, reload persistence (deliverable)

1 participant