Skip to content

feat(review): make comment replies deferred drafts, consistent with new comments - #213

Merged
huhamhire merged 3 commits into
devfrom
feat/inline-reply-drafts
Jul 14, 2026
Merged

feat(review): make comment replies deferred drafts, consistent with new comments#213
huhamhire merged 3 commits into
devfrom
feat/inline-reply-drafts

Conversation

@huhamhire

@huhamhire huhamhire commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Problem

Creating a new inline comment records a persisted draft (deferred, batch-published, survives switching PRs/files/tabs), but replying to a comment posted immediately with no draft. So an unsubmitted reply was lost on switching, and the two authoring flows were inconsistent.

Change

Unify replies onto the same draft model (chosen direction: replies become real drafts):

  • Data modelReviewDraft gains kind: 'comment' | 'reply' (absent = comment, back-compat) and replyTo { parentCommentId, threadId? }; anchor becomes optional (a reply snapshots its inline parent's anchor, or has none when replying to a summary comment).
  • Maindrafts:create validates the kind constraints; drafts:publishBatch branches reply → comments.replyToComment vs comment → publishInlineComment.
  • Shared reply editorCommentReplyEditor now creates a pending reply-draft instead of posting immediately, so every surface (activity timeline + inline diff) defers identically (honoring the "comment interactions identical across surfaces" rule via the shared component). A new shared ReplyDraftList renders a parent's pending reply-drafts below it (reusing DraftZone), self-fetching from the drafts store, on both surfaces.
  • Batch — reply-drafts join "Publish comments (N)" and its count; on successful publish the local draft is dropped and the real reply is fetched back as a normal comment.
  • GuardsuseDraftZones excludes reply-kind (they render nested, not as standalone line zones); useLineCommentAdder ignores them for + occupancy; DraftsPanel / PublishReviewModal show a "reply" tag and guard the now-optional anchor; PrPanel / DiffView guard anchor too.

Editing an existing comment stays immediate (it mutates a remote comment, not new authored content). i18n added for all 4 locales.

Notes

  • The comments:reply IPC/controller is retained (still used by the local API / CLI comment-write path); only the renderer stopped calling it directly.
  • This PR also folds in the inline-comment-zone teardown fix (fix(diff): preserve in-progress inline comment across comment refresh) — mountInlineZones now reconciles zones in place instead of tearing them down on every comment refresh, so an in-progress inline reply/edit isn't discarded. It's independent of the reply-drafts change (different files) and was merged in for convenience.

Verification

  • Gates green: lint (--max-warnings=0), typecheck, test (all suites pass), build.
  • Manual runtime check recommended: reply inline/activity → a pending reply-draft card appears under the parent → switch PR/file/tab and back, it persists → "Publish comments" posts it as a real reply.

🤖 Generated with Claude Code

huhamhire and others added 2 commits July 14, 2026 10:25
Inline comment view zones were fully torn down and rebuilt on every
comments:changed (a reply/edit/delete anywhere, or the poller pulling a
new remote comment). Each rebuild unmounted the zones' React roots, so an
open inline reply/edit lost its half-typed text — unlike the activity
timeline, which survives via keyed reconciliation.

Give the inline zones the same "reconcile in place" behaviour: refactor
mountInlineZones into a persistent controller (createInlineZones →
{ update, dispose }) that diffs zones by a stable (side, line) key —
unchanged keys re-render their existing root (CommentZone keys children by
remoteId, so an open editor keeps its state), only added/removed lines
mount/unmount. useCommentZones splits into a structural effect (owns the
controller, recreated only on editor/file/view change) and a content
effect (calls controller.update on comments/props change + rebuilds the
stateless glyph decorations).

The one-shot mountInlineZones wrapper is kept byte-compatible, so the
draft zones (useDraftZones) are unchanged. The activity timeline already
handled this and needs no change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ew comments

Creating a new inline comment records a persisted draft, but replying to a
comment posted immediately with no draft — so an unsubmitted reply was lost
on switching PRs/files/tabs, and the two authoring flows were inconsistent.

Unify replies onto the same draft model:

- ReviewDraft gains kind ('comment' | 'reply', default 'comment' for
  back-compat) + replyTo { parentCommentId, threadId? }; anchor becomes
  optional (a reply snapshots its inline parent's anchor, or has none when
  replying to a summary comment). drafts:create validates the kind
  constraints; publishDraftBatch branches reply -> comments.replyToComment
  vs comment -> publishInlineComment.
- The shared CommentReplyEditor now creates a pending reply-draft instead of
  posting immediately, so every surface (activity timeline + inline diff)
  defers identically. A new shared ReplyDraftList renders a parent's pending
  reply-drafts below it (reusing DraftZone), self-fetching from the drafts
  store, on both surfaces.
- Reply-drafts join the "Publish comments" batch and count; they publish via
  the reply API and, on success, the local draft is dropped and the real
  reply is fetched back as a normal comment.
- useDraftZones excludes reply-kind (they render nested, not as line zones);
  useLineCommentAdder ignores them for '+' occupancy; DraftsPanel /
  PublishReviewModal show a "reply" tag and guard the now-optional anchor;
  PrPanel / DiffView guard anchor too.

Editing an existing comment stays immediate (it mutates a remote comment,
not new authored content). i18n added for 4 locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@huhamhire huhamhire added the enhancement New feature or request label Jul 14, 2026
@huhamhire
huhamhire merged commit c3a1992 into dev Jul 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant