Selecting a group in "Additional groups" now auto-saves it to the
caller's favorites, and each favorite gets a × affordance for one-click
removal. Favorites still live where they always have —
config.webex.bot[app].authorized[personId].groups — so nothing changes
for existing installations.
Backend
- New POST /CollabCentral/:app/user/groups/add. Body { id }. Validates
the caller is authorized for :app, validates :id resolves to a real
Webex group by looking it up in the cached org-wide group list
(blocks arbitrary strings from being stuffed into config.json),
dedups against the existing favorites array, writes config.json via
saveConfig, and returns the updated array.
- New POST /CollabCentral/:app/user/groups/remove. Body { id }.
Filters that id out of the caller's favorites, writes only when
something actually changed (a remove of an unknown id no-ops instead
of rewriting config.json), and returns the updated array.
- Both endpoints are safe against concurrent writes: index.js is
single-process and node is single-threaded, so read/mutate/write
runs atomically per request.
- Both log a compact audit line (last-8 of personId + group name) so
operators can see who is curating what.
Frontend (sendMessage.html + .js + app.css)
- New "Manage favorites" chip strip renders directly under the
Favorite Groups picker. Each favorite becomes a pill (uses .alias
when set, otherwise .name; long labels truncate with ellipsis).
Clicking × on a pill removes that favorite server-side and updates
the strip locally.
- Additional Groups picker wires a 'change' listener that diffs the
current selection against the previous one and only fires
/user/groups/add for newly-picked ids (never re-fires on the
reselect side of a deselect+reselect, never spams the API with the
full selection on every keystroke).
- Client keeps favoriteGroups mirrored to every API response so the
"already a favorite?" dedup check is a pure in-memory lookup — no
wasted round trips when the user re-picks a group they already
favorited.
- Transient status message ("Added to favorites." / "Removed from
favorites." / error variants) fades under the field label; sticks
around ~4s.
- New shared styles: .fieldLabelRow (label + inline status),
.fieldStatus (with --error variant), .chipStrip, .chip,
.chip__label, .chip__close (with hover/focus states).
Co-authored-by: Cursor <cursoragent@cursor.com>