Skip to content

Add reviewable Suggested Edits to Content - #4274

Open
3mdistal wants to merge 10 commits into
mainfrom
codex/content-suggested-edits
Open

Add reviewable Suggested Edits to Content#4274
3mdistal wants to merge 10 commits into
mainfrom
codex/content-suggested-edits

Conversation

@3mdistal

@3mdistal 3mdistal commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Problem

Content editors need to propose changes without rewriting the shared page before review. Reviewers need to see exactly what changed, discuss it, and accept or reject it without losing concurrent edits. This PR adds that document-markdown workflow and repairs the editor, discussion, and persistence failures found while exercising it.

Incomplete checkpoint, pushed at Alice's request. Do not merge. Further investigation is deferred; this is not full product acceptance or a deployment.

Approach

Keep proposed edits separate from the canonical collaborative page until an authorized reviewer accepts them. Render pending changes beside their targets, retain their author and discussion identity, and reject ambiguous or stale application instead of silently overwriting newer content. This remains a document-markdown slice, not generic typed Revision support across every Content object.

What changed

  • Adds durable suggestion creation, amendment and review actions, additive schema support, permission checks, revision/basis checks, retry handling and review history.
  • Adds Suggesting mode, inline comparisons and anchors, proposal reopening, independent decisions, discussion filters, reactions and unread/mute controls.
  • Repairs draft persistence, keyboard exit and focus, marked text, paragraph/heading mapping, toolbar persistence, and concurrent collaborative reconciliation.
  • Blocks unsupported database, page, media and generation side effects while suggesting; existing media remains readable and commentable. Ordinary supported editing remains available outside Suggesting.
  • Repairs local transaction routing and development-server listener/timer disposal exposed during testing. Includes focused regressions, localized UI copy and package changesets.

Verification

The final bounded repair passed 37 slash-command tests, 201 editor/media tests, Content typecheck and targeted eligibility regressions. Both localization guards and diff whitespace checks passed. Earlier broader regression results and their scopes are retained in the governing QA note; they are not a claim that a new whole-suite run passed.

Owning-task native browser replay passed the database/page/image command exclusions, direct Generate guard, existing-image read-only controls in Suggesting, ordinary controls after exit, and text proposals beside images. Earlier unaffected desktop/mobile review, recovery and concurrent-edit evidence is retained. Bounded independent technical review closed its final generation-bypass finding; technical review does not override the browser failure below. New-head CI has not been accepted or monitored to completion.

Deferred failures and acceptance gaps

  • Code-block preview fails: enter Suggesting, choose /code, type text, then Stop. The saved Add displays “Preview unavailable” and “Highlight unavailable.” Reject preserved the canonical baseline. Root cause is not established. Resume with this reproduction and verify preview, anchoring, accept/reject and reload after repair. This remains an acceptance failure, not optional post-merge polish.
  • Cleanup is partial: fourteen primary QA roots have verified Restore entries, but one bypass-created database child remained active after Delete and reload. Its exact identifiers and recovery action are in the governing note. QA tabs, instrumentation and the owned server are closed.
  • Blocked/unverified: actual-agent attribution needs a working AI connection; source/local-file boundaries need representative fixtures; beta needs authorized deployment and role-separated access. Motion and screen-reader evidence remain unverified.
  • GitHub reported conflicts with main before this checkpoint. No conflict resolution, merge, branch rotation or deployment is included in this handoff.

Review focus

Before a later integration, resolve the code-block failure and merge conflicts, refresh affected verification, and examine canonical isolation, permission/basis races, unsupported side-effect boundaries and transaction rollback. No general Suggested Edits completion claim is made.

content_product_impact:
  lane: contract_fulfillment
  features:
    - content.feature.review-changes-in-place
  capabilities:
    - content.revision.suggestions
    - content.diff.in-place
  record_change: included
  proof:
    - Focused slash-command and editor/media regression suites passed
    - Native browser replay passed exclusion and existing-media safety paths
    - Code-block preview failed; complete acceptance remains blocked
  rationale: Implements and repairs the document-markdown suggestion slice without claiming generic Revision fulfillment or deployment acceptance.

@netlify

This comment has been minimized.

@3mdistal
3mdistal marked this pull request as ready for review September 3, 2026 18:16
@3mdistal
3mdistal enabled auto-merge (squash) September 3, 2026 18:16
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Visual recap — generation failed

The visual recap could not be generated for this pull request. This is informational only and does not block the PR.

Diagnostic:

No plan URL: create-visual-recap failed 422 Unprocessable Entity: {"error":"plan.mdx:52:297: Could not parse expression with acorn"}

Agent output: Repaired recap-source.json by closing the entities expression in the MDX DataModel block. --- ⠀ 🟢 Corrected recap source written; ready for CI’s retry. stderr: Reading additional input from stdin... 2026-09-03T18:29:52.341258Z ERROR codex_core::tools::router: error=apply_patch verification failed: Failed to find expected lines in /home/runner/work/agent-native/agent-native/recap-source.json: <DataModel id='suggestion-model' entities={[{id:'suggestions',name:'agent_review_suggestions',fields:[{name:'id',type:'text',pk:true,change:'added'},{name:'resource_type, resource_id',type:'text',change:'added'},{name:'adapter_kind, adapter_version, thread_id',typ…

@3mdistal
3mdistal disabled auto-merge September 3, 2026 18:39

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and found 8 potential issues 🔴

Review Details

Code Review Summary

PR #4274 adds a feature-flagged, durable Suggested Edits layer for Content, with proposal operations and threads kept separate from canonical document state until an authorized accept decision. The implementation also extends shared review actions, persistence, polling/Yjs synchronization, editor isolation, Comments history, permissions, localization, and documentation. The overall architecture is sensible: decision writes use compare-and-set semantics, accepted content is routed through canonical mutation state, and the feature is default-off. Because this PR mutates canonical user content and adds durable decision/idempotency behavior, I assessed it as High risk.

Key Findings

  • 🔴 HIGH — Accepted suggestions do not reconcile the primary Blocks field for allowed system-database documents, leaving database-backed content stale.
  • 🟡 MEDIUM — Suggestion create retries and concurrent duplicate creates are not reliably idempotent; decision retries can also fail after the first decision commits.
  • 🟡 MEDIUM — Suggesting mode can still persist a pasted title through the paste handler.
  • 🟡 MEDIUM — Unsupported structures and inline databases can be introduced through an ostensibly text-only accepted Markdown replacement.
  • 🟡 MEDIUM — Suggestion history has an unbounded N+1 query pattern, and the merged history is not chronologically interleaved.
  • 🟡 MEDIUM — The non-Postgres transactional sync path can reuse cursors and permanently skip events.

The ACL rechecks, CAS decision claim, canonical/Yjs publication ordering, and default-off flag wiring are good patterns. 🧪 Browser testing: Unable to execute — dev server is healthy, but Chrome MCP/browser automation tools are unavailable in this environment; 17 grounded cases were planned and all were marked environment-unverifiable.

});
await coordination.ydoc.persist(tx, nextContent);
await coordination.sync.persist(tx);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Accepted suggestions skip Blocks-field reconciliation

apply updates canonical documents.content and synchronization state, but does not perform the primary Blocks lock/identity reconciliation used by the normal canonical update path. The UI/action combination allows suggestions for system-database documents, so accepting one can leave the database-backed Blocks field on the old Markdown and cause stale or invalid content in database views. Run the same reconciliation inside this acceptance transaction and cover a system-database acceptance case.

Additional Info
Reported by 1 of 4 review agents; confirmed against the changed canonical acceptance path and system-database eligibility.

Fix in Builder

if (!adapter) throw new Error("Suggestion adapter not registered");
const adapterContext = { ...(ctx as any), suggestionAccess: access };
const operations =
(await adapter.validateProposal({ ...args, ctx: adapterContext })) ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Create retries stop being idempotent after the document changes

The action performs current-resource/base-revision and feature-flag validation before looking up the existing creation idempotency record. If the first create committed but its response was lost, a later document edit or flag change makes the same-key retry fail instead of returning the already-created suggestion. Resolve the creation mapping (with the required access check) before state-dependent validation for duplicate requests.

Additional Info
Reported by 2 of 4 review agents.

Fix in Builder

},
tx,
);
await recordSuggestionCreation(tx, args.idempotencyKey, created.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Concurrent duplicate creates can fail on the idempotency key

Two identical creates can both observe no existing creation record before either transaction inserts it, then race on the unique idempotency key. The loser receives a constraint error rather than the winner's suggestion, so a normal concurrent retry is reported as a failed creation. Claim the key atomically or recover the committed winner on conflict and return it after verifying request identity.

Additional Info
Reported by 2 of 4 review agents.

Fix in Builder

current.id,
args.decision,
);
if (!claimed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Concurrent duplicate decisions are rejected after the first commit

Two requests with the same suggestion, decision, and idempotency key can both read the pending row; after the first CAS claim commits, the second gets zero affected rows and unconditionally throws. This breaks retry-safe decision semantics for concurrent network retries. When the claim fails, reload the suggestion and idempotency record in the transaction and return the recorded same decision, while still rejecting a conflicting decision.

Additional Info
Reported by 1 of 4 review agents; the race follows directly from the CAS failure branch.

Fix in Builder

Comment thread templates/content/app/components/editor/DocumentEditor.tsx
"Pages containing inline databases cannot accept suggestions yet",
);
}
replacePreparedCollabContent(coordination.ydoc, nextContent);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Accepted Markdown can introduce unsupported content

The exclusion checks inspect the current content and the proposal's before.markdown, but the resulting after.markdown is not structurally validated before it replaces canonical content. A nominal text replacement can therefore add an inline database, media, embed, or other unsupported structure and bypass the v1 suggestion boundary. Validate the parsed resulting document and reject changed unsupported nodes/marks before mutation.

Additional Info
Reported by 2 of 4 review agents.

Fix in Builder

})
).rows;
return (
await Promise.all(rows.map((row) => getSuggestion(String(row.id), client)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Suggestion history performs unbounded N+1 queries

listSuggestions loads all matching IDs and then calls getSuggestion for each, which performs additional suggestion and operations queries. The editor requests unfiltered history without pagination, so long-lived documents cause 1+2N database round trips and unbounded concurrent work while transferring full before/after snapshots. Batch the rows/operations and bound or paginate the history payload.

Additional Info
Reported by 3 of 4 review agents.

Fix in Builder

</DropdownMenuContent>
</DropdownMenu>
</div>
{renderSuggestionCards(historySuggestions)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 All discussions does not merge history chronologically

The combined history renders all matching suggestion cards first and regular comment threads afterward rather than interleaving the two collections by creation time. This makes the “All discussions” view misleading for mixed activity even though its filters apply to both types. Build one filtered, timestamp-ordered history sequence before rendering.

Additional Info
Reported by 1 of 4 review agents; aligns with the PR's merged Comments history behavior.

Fix in Builder

@3mdistal
3mdistal marked this pull request as draft September 3, 2026 19:21
# Conflicts:
#	templates/content/app/components/editor/DocumentEditor.tsx
#	templates/content/app/i18n-data.ts
…ed-edits

# Conflicts:
#	packages/core/src/collab/storage.spec.ts
#	packages/core/src/collab/storage.ts
#	packages/core/src/review/store.ts
#	templates/content/app/components/editor/DocumentEditor.layout.test.ts
#	templates/content/app/components/editor/DocumentEditor.tsx
#	templates/content/app/components/editor/VisualEditor.tsx

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and has a few items to flag 🟡

Review Details

Incremental Code Review Summary

The latest PR head substantially expands the Suggested Edits editor experience with contextual anchor resolution, fail-closed ambiguous matching, inline decorations, proposal-creation locking, narrow-drawer event isolation, exact-thread selection, and improved discussion filtering. The overall direction addresses the previous UX concerns, but this incremental review session was interrupted before the independent code-review workers could return findings, so I am not asserting that any previously open comments are fixed or reposting them. Existing unresolved review threads remain the source of truth until they are verified against the new head.

No new findings are submitted from this interrupted pass. 🧪 Browser testing: Unable to execute — browser automation tooling is unavailable in this environment; the feature remains UI-impacting and should be rerun with Chrome MCP available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant