Skip to content

[Dev -> Stage] T3-26.23#170

Merged
qiyundai merged 62 commits into
stagefrom
dev
May 29, 2026
Merged

[Dev -> Stage] T3-26.23#170
qiyundai merged 62 commits into
stagefrom
dev

Conversation

@qiyundai
Copy link
Copy Markdown
Collaborator

Release: T3-26.23 (stage)

Promotes dev into stage for the T3-26.23 stage release.

Release notes

Scope configs (new admin surface)

Event form

RSVP / registration

Series

Access (RBAC)


Included merges (high level)

Area PRs
Configs platform #129, #159, #160, #162, #167, #168, #169
RSVP shape migration #164#166
Event form UX #148, #152, #156, #158
Series / taxonomy #163, #169
RBAC #161

Promotion path: dev is 62 commits ahead of stage at PR open.

Verification

  • Config Management: CRUD for RSVP, locales, custom attributes on a test scope
  • Event form: create/edit with scope configs, custom attributes, publish guard
  • New event save redirects to edit URL; no duplicate events on re-save
  • Series publish/unpublish preserves scopeId
  • RBAC group search shows members when searching by group name

Post-merge: merge the companion T3-26.23 Production Release PR (stagemain) after stage validation.

Made with Cursor

qiyundai and others added 30 commits April 7, 2026 22:44
…tion heading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Collapsible RSVP field cards in Create/Edit dialog (only select/multi-select; non-option fields always expanded)
- Locale switcher in RSVP toolbar; all localizable fields reflect selected locale; per-field edit dialog handles locale-mode saves (base + override in one API call); Localizations section removed from RSVP dialog
- RSVP expanded options: replace numbered text list with read-only/editable TextFields (Value + Label)
- Custom attr expanded values: same inline edit pattern as RSVP options (Value + Label TextFields, Edit/Save/Discard/Add/Remove)
- displayAs: constrained to valid options per field type via getDisplayAsOptions(); column suppressed for non-select types; reset on incompatible type change
- Badge fixes: informative → neutral for type/inputType labels; wrapped in flex div to prevent full-width stretch in table columns; locale code column uses plain Text
- isRowExpandable predicate on DataTable/ResourceDashboardLayout: suppress expand chevron for RSVP fields with no options/rules/default/locale-overrides, and for custom attrs with no values
- Custom attr dialog: size="L"
- Fix Update button bug in custom attr dialog: normalize undefined label on openAttrEdit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders scope-configured custom attributes (text, boolean, single-select,
multi-select) in a new form card at the bottom of the Additional Content
step. Multi-select uses an ordered repeater pattern to support user-defined
downstream sequencing. Card always visible — shows progress indicator
while loading and an empty-state message when no attributes are configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s to scope configs API

- Update scope-configs integration guide: options now use {value, label} format,
  add localization merge patterns and option display logic notes
- Migrate RegistrationFieldsComponent from fetching external JSON configs to
  cachedApi.getConfigsForScope, using RsvpFormField labels directly
- Update RegistrationConfigComponent prop name cloudType → isExperienceCloud

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ields

When a locale is selected in the table toolbar, the Edit RSVP Config
dialog now shows and edits locale-specific labels, placeholders, and
option labels instead of base values. Structural controls (add/remove
field, add/remove option, option values) are hidden in locale mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l width

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed indicator

Remove isRequired from agenda end time fields (TimeField and DatePicker).
Add a publish guard that validates all required fields across form steps
before allowing publish, including custom attributes with isRequired.
Shows a user-friendly dialog listing missing fields grouped by step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- README/PROJECT_OVERVIEW: port 3000, real npm scripts, structure
- EVENT_FORM/DEVELOPMENT_WORKFLOW/DEV_TOKEN: URLs and eventType route
- FRONTEND: S2, App routes table, TopNav-based new-route steps
- TESTING: Jest layout, test:unit, CI as lint+type-check, no e2e script
- TOP_NAV_LAYOUT/USER_PANEL: paths, nav items, remove SideBar confusion
- MODULAR_COMPONENT_PATTERN/EVENT_FORM: pages/EventForm paths

Made-with: Cursor
EventInfoComponent previously called cachedApi.getLocales(), which hits the ESP GET /v1/locales endpoint. That route is deprecated in events-service-platform in favor of scope-level configs with type "locales" (same LocalesScopeConfig shape as Cloud Management: localeNames + localeUrlCodes).

The picker now resolves the active ExC group scope via useGroup().activeGroup.scopeId and loads configs with cachedApi.getConfigsForScope(scopeId, 'locales'). When a locales config exists and localeNames is non-empty, options are built from those entries. If there is no scope id, the request fails, or no locales config is present, we fall back to SUPPORTED_SPEAKER_LOCALES + SPEAKER_LOCALE_LABELS so behavior matches ConfigManagement’s RSVP "available locales" fallback.

If the event’s current defaultLocale is not listed in the resolved set (e.g. legacy data), we inject a synthetic picker row so the selected key remains valid until the user changes language.

Made-with: Cursor
Align ConfigType, scope config models, and create bodies with ESP develop (customAttributes instead of custom-attributes).
Update Event Form and Config Management fetch/filter/create paths so ?type= and config type match ESP’s strict filter.
- Prefer scope RSVP config via getConfigsForScope; fall back to legacy cloud
  JSON (configService + mapLegacyRsvpConfigToFormFields) when scope has no fields
- Add RsvpFieldOptionSelectionState on EventFormData for select/multi-select
  option order and include toggles (client-only until ESP exposes a contract)
- S2 Disclosure for option lists; HTML5 DnD reorder; auto-remove field when all
  options disabled; reset option state when field hidden
- RegistrationConfigComponent wires cloudType and rsvpOptionSelections patch handler
- Hydrate rsvpOptionSelections as {} from API; TODO(PIM) on save path for future
  granular serialization

Co-authored-by: Cursor <cursoragent@cursor.com>
- Document pim context/report usage and pod binding in CLAUDE.md
- Add Claude Code sync command for PIM context pull
- Gitignore .pim/ directory for local PIM state

Co-authored-by: Cursor <cursoragent@cursor.com>
- Options list now renders inside the field card border rather than below it
- Replaced Disclosure/DisclosureTitle trigger with an inline ListBulleted icon in the field row (5th grid column)
- Icon is only shown for fields that have selectable options; absent otherwise
- Icon highlights in selection-ring color when panel is expanded
- Starting a field drag collapses all open option panels (mutually exclusive)
- Removed redundant borders from option rows; drag-over ring retained

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocking fixes:
- Replace div[role=button] with ActionButton isQuiet + onPress to fix drag-start
  interference and missing Spectrum focus ring
- Add e.stopPropagation() to handleOptionDragOver/handleOptionDrop so option drags
  don't bubble to the outer field-card drop target
- Clear optionDrag/optionDragOver in handleDragStart to avoid stale state
- Prune expandedOptions when a field is hidden so the panel doesn't auto-reopen on re-show

Code quality:
- Extract toggleExpanded local fn to deduplicate setExpandedOptions logic
- Replace <div /> fallback with null for the empty list-icon column
- Wrap mandatedFieldNames in useMemo; use stable ref in useEffect dep array
- Make isSelectableField a proper TypeScript type predicate
- Add aria-label/aria-hidden to Move drag handle divs
- Remove dead font styles from empty header span elements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(rsvp): move options panel inside field card with inline toggle
…t-route

# Conflicts:
#	web-src/src/pages/EventForm/EventForm.tsx
[MWPW-194949] : Update route with event id
Merge publishing-profile metadata with catalogue acknowledgments in one
update when possible, backfill when the catalogue loads later, reset
profile state on eventId change, and merge profileId from ESP association
when the nested profile omits it (follow-up to explicit PPN selection).

Co-authored-by: Cursor <cursoragent@cursor.com>
After saving a new image the table showed the stale photo because
handleFormSubmit called apiService directly, bypassing cachedApi and
its cache-invalidation logic. Removing an image also had no effect
because no DELETE was ever sent to ESP.

- Use cachedApi.updateSpeaker/createSpeaker so the series cache is
  invalidated before loadSpeakers re-fetches
- Track removedImageId in SpeakerFormDialog and pass it to the parent;
  handleFormSubmit now calls deleteSpeakerImage when a saved image is removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

fix(event-form): stabilize page metadata (PPN) acknowledgment hydration
qiyundai and others added 28 commits May 21, 2026 10:48
Merge dev into configs branch (resolves conflicts, preserves both feature sets)
When searching by group name, the expanded member list was always empty
because the same query term was applied to filter users — who naturally
don't match the group name. Now, if the group itself matches the query
(name or description), all its members are shown; filtering is only
applied when the group surfaced via a member match.

Also adds a defensive empty-string guard to groupMetaMatchesQuery,
matching the style of the parallel userMatchesQuery helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(scope-group-mgmt): show all members when group matched by name
Three structural correctness fixes around rsvpFormFields:
- Add 'rsvpFormFields' to speciallyHandledFields so the generic
  EVENT_DATA_FILTER loop does not double-process the field alongside
  the explicit payload block.
- Guard rsvpFormFields payload assembly with Array.isArray so empty
  arrays and non-array form state values are not serialized as
  { fields: [] } (BE rejects non-object/empty values).
- Surface the actual BE error message on save failures by reading
  result.error.message instead of result.message, which was always
  undefined and caused all save errors to fall back to the generic
  'Failed to update/create event' string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds transitional dual-shape support for rsvpFormFields while ESP
BE PR #903 (https://git.corp.adobe.com/wcms/events-service-platform/pull/962/files
sibling: PR #903) is in flight. The BE is moving from the legacy
{ required: string[], visible: string[] } shape to the new
{ fields: [...] } shape — a breaking change. Until #903 ships to
dev/stage/prod and stored events are normalized, EMC must work
against both shapes.

Save side (useEventFormSave.ts):
- isRsvpFormFieldsRejection() detects 400 responses whose validation
  errors point at rsvpFormFields (Ajv errors don't include the field
  name in `message`, so we also walk `errors[].instancePath`).
- toLegacyRsvpFormFieldsPayload() rewrites the new {fields} payload
  to the legacy {required, visible} shape. Per-field `options`
  overrides aren't representable in the legacy shape and are dropped
  with a console.warn so QA can see the loss.
- Update and create branches send the new shape first and retry with
  the legacy payload on rsvpFormFields-flavored 400s.

Read side (eventFormMappers.ts):
- readRsvpFormFields() adapts either GET shape to the array form the
  form state expects. Without it, events stored under the legacy
  shape would open with an empty RSVP fields list.

All temporary code is tagged `rsvp-shape-migration` so the cleanup
after #903 ships is a clean grep-and-delete; the structural fixes
from the prior `fix: rsvp form fields handling` commit are permanent
and must stay (see header comment in useEventFormSave.ts for the
checklist).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix: config API bugs, ESP PR #962 custom attributes, scope mismatch fix
[MWPW-195009]:- Custom CaaS Taxonomy View per Series
…-migration

chore: rsvpFormFields BE shape migration shim
…ds-be-shape-migration

Revert "chore: rsvpFormFields BE shape migration shim"
Re-applies transitional dual-shape support for rsvpFormFields with save
order corrected: legacy {required, visible} shape is sent first; the new
{fields} shape is the fallback retry on rejection. Previous version was
reverted because it sent new shape first, which fails against the current
BE before ESP PR #903 ships.

Save side (useEventFormSave.ts):
- Primary call converts rsvpFormFields to legacy shape before sending.
- Retry with new {fields} shape only if BE rejects with a
  rsvpFormFields-flavored 400 (post-#903 environments).

Read side (eventFormMappers.ts):
- readRsvpFormFields() adapts either GET shape to the array the form
  expects, so events stored under the legacy shape open correctly on edit.

All temporary code tagged `rsvp-shape-migration` for clean grep-and-delete
once ESP PR #903 ships everywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-migration-new

chore: rsvpFormFields BE shape migration shim (legacy-first order)
ESP enforces one config per scope (any combination of rsvp / locales /
customAttributes slices), but the UI treated each tab as its own config
keyed on a legacy `type` discriminator. When a scope already had a
config (e.g. customAttributes), saving from the Locales tab POSTed and
returned 409 because the "no locales-typed config found" check missed
the existing customAttributes-typed one.

This change unifies the model: a scope has a single ScopeConfig with
optional slice fields. Slices are detected by field presence, and saves
PUT to the existing config (merging slices, dropping legacy `type`) or
POST a fresh one only when the scope owns none.

## Summary
- New unified `ScopeConfig` with optional slice fields + `hasRsvpSlice`
  / `hasLocalesSlice` / `hasAttributesSlice` guards
- `getConfigsForScope(scopeId, type)` filters by slice-field presence
  so reads still work on configs written by newer PUTs that omit `type`
- ConfigManagement save handlers (RSVP / Locales / Attributes) PUT into
  `scopeConfig.configId` when the scope owns a config; POST otherwise
- New `buildPutBody()` strips legacy `type` while preserving sibling
  slices on every PUT path
- Slice-level deletes (`deleteSlice`, `handleDeleteAttr`) clear only the
  removed slice via PUT when other slices remain; full DELETE only when
  the slice was the last one
- EventForm consumers (EventInfo, CustomAttributes, RegistrationFields)
  switched from `c.type === ...` to slice-presence helpers

## Test plan
- [ ] On a scope that already has a customAttributes config, go to the
      Locale Mapping tab, click Create Locales Config, fill in en-US,
      and save — expect a PUT 200 (not POST 409), and after refresh both
      the locales table and the existing custom attributes are visible
- [ ] On a fresh scope (no config), Create Locales Config — expect a
      POST 201 with no `type` field in the body
- [ ] On a scope with locales + attributes, delete the locales config —
      expect a PUT 200 that drops localeNames/localeUrlCodes; attributes
      remain on refresh
- [ ] On the same scope, delete the last custom attribute — expect a
      PUT 200 that drops `attributes`; locales remain
- [ ] On a scope with only locales, delete locales — expect a DELETE 204
- [ ] Repeat the same scenarios for the RSVP slice (Create/Edit field,
      Edit options, Delete field, Delete RSVP Config)
- [ ] Open EventForm on a scope whose config was PUT without `type` —
      locale picker, custom attributes, and RSVP fields all populate

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Only Locale Mapping is in scope; the other two tabs are disabled and
the default landing tab switches to `locales` so the page doesn't open
onto a disabled tab.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the object-shaped locales slice (`localeNames` + `localeUrlCodes`
keyed by locale code) with a single `locales: Locale[]` array on
ScopeConfig, where each entry carries `{ code, name, folder }`. Hard
cutover — old fields are no longer read or written anywhere on the FE.

Touches:
- types/configApi.ts: new `Locale` interface; `ScopeConfig.locales?`
  replaces the two record fields; `LocalesScopeConfig` and
  `hasLocalesSlice` updated.
- services/api.ts: `getConfigsForScope('locales')` filters by
  `Array.isArray(c.locales)`.
- pages/ConfigManagement: read/write/delete paths use the array;
  Locale Mapping table renders from `locales`; dialog state field
  renamed `urlCode` -> `folder`; column header "URL Code" -> "Folder".
- pages/EventForm/EventInfoComponent: language picker reads
  `localesConfig.locales` instead of `localeNames`.

Pending BE: this change cannot be verified against a live backend until
ESP migrates the locales slice; existing scopes will fall back to the
default picker / empty state until then.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(configs): PUT existing scope config when adding a new slice
ESP returns scopeId on GET but SERIES_DATA_FILTER stripped it on PUT,
causing scope membership to be lost on series update/publish/unpublish/
archive. Add scopeId to the filter (updatable, not cloneable) and thread
it from the loaded canonical response into SeriesForm's buildApiPayload.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(series): include scopeId in PUT payload
chore(configs): migrate locales slice to array shape
@qiyundai qiyundai merged commit 1f56ed1 into stage May 29, 2026
2 checks passed
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.

4 participants