Skip to content

ERA-12077: Realtime handling for subject create/deactivate#1620

Open
jeslefcourt wants to merge 5 commits into
developfrom
ERA-12077
Open

ERA-12077: Realtime handling for subject create/deactivate#1620
jeslefcourt wants to merge 5 commits into
developfrom
ERA-12077

Conversation

@jeslefcourt

Copy link
Copy Markdown
Contributor

ERA-12077 — Realtime handling for subject create / deactivate (UI)

Handles two new realtime socket messages so subjects appear/disappear live on the map and in the subject-group sidebar, without waiting for the next REST refetch. Mirrors existing new_event / subject_status handling.

Companion backend PR: PADAS/das#ERA-12077 (link added after creation).

What changed

  • withSocketConnection/useRealTimeImplementation/config.js — maps new_subjectsocketNewSubject, delete_subjectsocketDeleteSubject.
  • ducks/subjects.js — new action types/creators and reducer handling across three stores:
    • subjectStore — insert (with name = title || name normalization matching the fetch path, null-last_position guarded) / remove.
    • mapSubjects.subjects — add/remove the id so the map selector surfaces/hides the subject.
    • subjectGroups — recursive insert into the nodes named in subject_group_ids, recursive remove on delete. Helpers are non-mutating; removeSubjectIdFromGroups preserves references for untouched subtrees to keep selector memoization intact.

Message contract

new_subject    → { type, subject_id, subject_data: {…full subject record…}, subject_group_ids: [...] }
delete_subject → { type, subject_id, subject_data: null }

Design note — divergence from the AC (needs reviewer/PM sign-off)

The ticket AC suggested reusing the existing subject_status message + an is_active check. This PR consumes dedicated new_subject / delete_subject messages instead (see the backend PR for rationale). Behaviour delivered is equivalent.

Testing

  • ducks/subjects.test.js — insert/remove across all three stores; idempotency; delete-of-unknown no-op; missing/empty subject_group_ids; unknown/unloaded group ids skipped without crash; nested subgroup handling; name-from-title normalization incl. null last_position; mutation-safety assertions; reference-preservation for untouched subtrees.
  • withSocketConnection/useRealTimeImplementation/config.test.js — the two new message types map to the correct action creators.
  • 39 reducer/normalization tests + config tests pass.

🤖 Generated with Claude Code

Consumes new_subject / delete_subject realtime socket messages:

- subjectStore insert (with name=title||name normalization, null
  last_position guarded) / remove.
- mapSubjects.subjects add/remove so the map selector surfaces/hides
  the subject.
- subjectGroups recursive insert into nodes in subject_group_ids and
  recursive remove on delete; helpers are non-mutating and preserve
  references for untouched subtrees.
- config maps new_subject/delete_subject to their action creators.

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

Copy link
Copy Markdown
Contributor Author

Companion backend PR: PADAS/das#4046

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

🚀 PR Environment Deployed

App Sync Health Image
pr-web-era-12077 ✅ Synced ✅ Healthy 0ecbd55782752107c4b9b2828c432ef3d284dda6

Access: https://era-12077.dev.pamdas.org

View in ArgoCD

Copilot AI 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.

Pull request overview

Adds realtime support for subject lifecycle changes so newly created and deactivated subjects appear/disappear immediately in the UI (map + subject-group sidebar) by handling new socket message types and updating the relevant Redux slices.

Changes:

  • Added new_subject / delete_subject socket message mappings to dispatch new subject actions.
  • Implemented reducers/action creators to insert/remove subjects across subjectStore, mapSubjects.subjects, and subjectGroups.
  • Added Jest coverage for the new socket config mappings and reducer behaviors (insert/remove/idempotency/nested groups).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/withSocketConnection/useRealTimeImplementation/config.js Maps new_subject/delete_subject socket messages to new subject action creators + socket health updates.
src/withSocketConnection/useRealTimeImplementation/config.test.js Verifies the new socket message types map to the correct handlers.
src/ducks/subjects.js Adds new socket action types and reducer handling for subject insert/remove across subject store, map subjects list, and subject group tree.
src/ducks/subjects.test.js Adds reducer/action creator tests for new subject insert/remove behavior across all affected stores.

Comment thread src/ducks/subjects.js Outdated
Comment thread src/ducks/subjects.js
Comment thread src/ducks/subjects.js
jeslefcourt and others added 3 commits June 29, 2026 11:17
…dates

Addresses code-review feedback: the new reducer paths allocated fresh
objects/arrays even when a realtime message wouldn't change state, which
defeats memoized selectors and causes avoidable re-renders.

- addSubjectIdToGroups now returns the original group/array references when
  the subject isn't appended anywhere (mirrors removeSubjectIdFromGroups).
- subjectStore SOCKET_DELETE_SUBJECT returns the original state when the id
  is absent (true no-op).
- mapSubjects SOCKET_NEW_SUBJECT/SOCKET_DELETE_SUBJECT short-circuit to the
  existing state when membership wouldn't change.
- Adds reference-equality tests for each no-op path.

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

updateSubjectLastPositionFromSocketStatusUpdate dereferenced
subject.last_position.radio_state, which threw for a subject inserted via
new_subject with last_position: null. Guarded the null access (and
coordinateProperties), so the first subject_status populates last_position
and the map marker renders. Adds merge + integration regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeslefcourt
jeslefcourt requested a review from luixlive June 29, 2026 05:19

@luixlive luixlive 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.

A couple minor improvement suggestions 👍

Comment thread src/ducks/subjects.js
(group) => group.id === groupId || isGroupIdInTree(group.subgroups, groupId)
);

const socketNewSubjectAction = (payload) => ({ type: SOCKET_NEW_SUBJECT, payload });

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.

This one liner is not being reused or abstracting anything really, we could keep it as:

dispatch({ type: SOCKET_NEW_SUBJECT, payload });

Comment thread src/ducks/subjects.js
return lastKnownMapSubjectValue;
}, INITIAL_MAP_SUBJECT_STATE);

// Recursively removes a subject ID from every node in the groups tree.

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.

This and the next comments are good for clarifying what the method does, but are too verbose and get deep in details. Let's keep our comments concise 👍

// Recursively removes a subject ID from every node in the groups tree.
// Returns the original array/group reference when nothing changed.

// Recursively appends subjectId to the subjects array of any group node whose
// id is in targetGroupIds. Idempotent. Returns the original array/group
// reference when nothing changed.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants