Skip to content

feat(annotations): apply sidebar lifecycle events to local store#762

Draft
jackiejou wants to merge 4 commits into
box:masterfrom
jackiejou:feat/sidebar-inbound-listeners
Draft

feat(annotations): apply sidebar lifecycle events to local store#762
jackiejou wants to merge 4 commits into
box:masterfrom
jackiejou:feat/sidebar-inbound-listeners

Conversation

@jackiejou
Copy link
Copy Markdown
Collaborator

Stacked on #761 (which is in turn stacked on #760). Until #761 merges, the "Files changed" tab will include the eventing and reply edit/delete commits. After both parents merge I will rebase this branch onto master and the diff will collapse to only the inbound listener changes. Please review only after #761 lands.

Summary

  • Listens to inbound sidebar lifecycle events from the activity feed and applies them to the document-side redux store, so the in-document popup stays in sync with sidebar-initiated edits, replies, and resolve/unresolve flows.
  • Four new apply* actions (plain createAction) dispatched from BaseAnnotator listeners onto matching reducer cases on annotations.byId:
    • Annotation update merges the partial payload, skipping explicit-undefined keys so a sparse update cannot erase fields like permissions or status.
    • Reply create dedupes by reply.id.
    • Reply update merges in place by reply.id.
    • Reply delete filters by replyId.
  • New SidebarEvent enum keeps the sidebar.* event names disjoint from Event.
  • Annotation delete is unchanged; it continues to flow through the existing annotations_remove listener and removeAnnotationAction reducer.
  • Eventing middleware is intentionally NOT updated; a new test.each regression asserts the four apply* actions never appear in the handler map.
  • V1 simplifications: only success-phase events are subscribed (no _start optimistic events); reply create dedupes by reply.id only; reducer bails when the parent annotation is not in local state.

Test plan

  • In a host app with a linked build, edit an annotation message in the activity feed and verify the in-document popup text updates inline.
  • Resolve and unresolve in the feed and verify the popup reflects the new status.
  • Delete an annotation from the feed and verify the popup disappears (handled by the pre-existing annotations_remove listener).
  • Add a reply in the feed and verify the new reply appears at the bottom of the popup thread.
  • Edit a reply in the feed and verify the reply text updates in the popup.
  • Delete a reply in the feed and verify the reply disappears from the popup.
  • Repeat each scenario in the popup and verify the feed updates exactly once with no follow-on apply* dispatches (loop check via redux DevTools).
  • yarn jest --testPathPattern="(store/annotations|store/eventing|common/BaseAnnotator)" passes (146 tests across 17 suites).

jackiejou added 4 commits May 29, 2026 12:06
Threads in PopupV2 can now edit and delete replies. Reply edits dispatch
to PUT /undoc/comments/{id} and reply deletes to DELETE
/undoc/comments/{id} via the box-ui-elements ThreadedComments client.

- Adds updateReplyAction and deleteReplyAction thunks. Both reject
  upfront when the reply is not present in state so callers receive a
  typed error instead of an opaque server response.
- Adds reducer cases that replace or remove the targeted reply on
  fulfilled.
- Threads the canEdit permission through replyToTextMessage so the
  edit affordance reflects backend permissions.
- Widens the Token type with TokenMap so consumers can pass a per-typed
  file id resolver for avatar fetching.
replyToTextMessage was not setting updatedAt on the message, so the
edit indicator never appeared on edited replies even though the
backend returns modified_at on the comment object. The Reply type
also did not declare the field.
box-annotations only emitted lifecycle events for the create flow.
Other thunks ran their API calls and updated internal state, but
withAnnotations listeners on the box-ui-elements side never fired,
so the activity feed went stale until reload.

Adds outbound eventing for five thunks following the existing
create.ts pattern:

- updateAnnotationAction emits annotations_update (covers resolve
  and unresolve via annotation.status)
- deleteAnnotationAction emits annotations_delete
- createReplyAction emits annotations_reply_create
- updateReplyAction emits annotations_reply_update
- deleteReplyAction emits annotations_reply_delete

Each handler emits via the singleton eventManager with the
{ annotation, error, meta: { requestId, status } } envelope. Reply
handlers add an annotationReply field carrying the reply object.

Generalizes AsyncAction over arg/payload so each handler can type
its action precisely. Each success-phase handler short-circuits
when the necessary payload is missing instead of emitting a
contradictory success event.
Listens to inbound sidebar.* events emitted by the activity feed and
applies them to the document-side redux store, keeping the in-document
popup in sync with sidebar-initiated edits, replies, and resolve flows.

- New SidebarEvent enum with four entries: SIDEBAR_ANNOTATION_UPDATE,
  SIDEBAR_REPLY_CREATE, SIDEBAR_REPLY_UPDATE, SIDEBAR_REPLY_DELETE.
- Four plain createAction actions (apply*) dispatched on each event.
- Four idempotent reducer cases on annotations.byId: merge for
  annotation update, dedupe-by-id for reply create, in-place merge for
  reply update, filter-by-id for reply delete. All bail when the
  parent annotation is missing from local state.
- The annotation update reducer skips explicit-undefined payload keys
  so a sparse update cannot erase fields like permissions or status.
- BaseAnnotator subscribes only to the success-phase events and
  unsubscribes in destroy().
- Annotation delete is unchanged; it continues to flow through the
  existing annotations_remove listener.
- Middleware regression test asserts the new actions are absent from
  the eventing handler map.
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