Skip to content

fix: resolve duplicate members and missing leader actions on chapter pages#666

Merged
bokelley merged 1 commit into
mainfrom
bokelley/german-chapter-lead
Jan 7, 2026
Merged

fix: resolve duplicate members and missing leader actions on chapter pages#666
bokelley merged 1 commit into
mainfrom
bokelley/german-chapter-lead

Conversation

@bokelley

@bokelley bokelley commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixed duplicate members appearing in chapter/working group member lists when users belong to multiple organizations
  • Fixed leader actions (Manage Posts button, etc.) not appearing for leaders because user ID wasn't being sent to the frontend

Root Cause

Bug 1 - Duplicate Members: SQL queries in working-group-db.ts used LEFT JOIN organization_memberships which returned multiple rows when a user belongs to multiple organizations. David Porzelt appeared twice because he's a member of 2 organizations.

Bug 2 - Missing Leader Actions: The /api/config endpoint wasn't returning the user's id field. The frontend checks currentUser.id === leader.user_id, but currentUser.id was always undefined.

Changes

  • server/src/db/working-group-db.ts: Changed plain LEFT JOIN organization_memberships to LEFT JOIN LATERAL ... LIMIT 1 in three queries:

    • getMembershipsByWorkingGroup()
    • getLeaders()
    • getLeadersBatch()
  • server/src/http.ts: Added id: req.user.id to the /api/config response

Test plan

  • All existing tests pass (187 unit tests)
  • Build succeeds
  • Verified API returns user ID via browser testing
  • Code review found no critical issues

🤖 Generated with Claude Code

…pages

Fixed two issues reported by David Porzelt on the German chapter page:

1. Duplicate members in chapter lists - Users belonging to multiple
   organizations appeared multiple times due to a SQL JOIN that returned
   multiple rows per user. Fixed by using LEFT JOIN LATERAL with LIMIT 1
   to ensure only one organization membership row per user.

2. Leader actions not working - The /api/config endpoint was not returning
   the user's ID, causing the frontend leader check to always fail since
   currentUser.id was undefined. Added id field to the response.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bokelley bokelley merged commit ecd9fc6 into main Jan 7, 2026
6 checks passed
bokelley added a commit that referenced this pull request May 12, 2026
…lder (#4424)

* feat(admin): add Collections and Placements tabs to adagents.json builder (#4420)

Adds v3 parity for collections[] and placements[] surfaces to the
adagents.json builder tool (deferred from PR #4415 by expert consensus).

- Collections tab: 8-field form (collection_id, name, kind, language,
  status, cadence, description) + raw JSON escape hatch for advanced fields
- Placements tab: placement_id, name, description, property_ids checkbox
  list from state.properties, property_tags, placement_tags chip input
- Placement Tags sub-section within Placements tab (same chip+delete
  pattern as existing Signal Tags tab)
- Agent modal: optional Placement constraints section (placement_ids and
  placement_tags checkboxes — overlays on any property-scoped auth type)
- Import (importFile, startManaging) and export (exportFile,
  updateJsonPreview) all round-trip collections, placements, placement_tags
- All card renders use createElement/textContent for XSS safety

No backend or schema changes. server/src/adagents-manager.ts already
emits v3 and is field-agnostic.

https://claude.ai/code/session_01Fkmv4wwAtqD1JoKeaioXgr

* fix(admin): address pre-PR review blockers on collections/placements builder tabs

- Revert spurious adcp_version downgrade in static/schemas/source/index.json
- Strip reserved keys (collection_id, name, etc.) from advanced textarea before
  Object.assign in saveCollectionForm; prevents silent override of validated fields
- Add duplicate-ID guards on saveCollectionForm and savePlacementForm
- Convert state.placementTags from string[] to {tag:{name,description}} map,
  preserving imported metadata on round-trips (import/export/startManaging)
- removePlacementTag cascade-cleans pl.tags[] on every placement (mirrors removeSignalTag)
- createPlacementTag normalises input (.toLowerCase + /[^a-z0-9_]/ → '_')
- savePlacementForm validates property_tags pattern; normalises placement tags on save
- escapeHtml() all user-originated values in innerHTML template literals:
  showPlacementForm (prop.property_id, prop.name), updateAgentPlacementCheckboxes
  (pl.placement_id, pl.name, tag), renderPlacementTags (tag, meta.description)
- Fix hardcoded #666 colours → var(--color-text-muted) in placement checkbox empties
- Expand changeset body with full change description

https://claude.ai/code/session_01Fkmv4wwAtqD1JoKeaioXgr

* fix(admin): renderPlacementTags XSS via attribute-context escape bypass

Post-expert-review finding on PR #4424:

renderPlacementTags built its delete button as
  onclick="removePlacementTag('${escapeHtml(tag)}')"
escapeHtml encodes ' to &#39;, but the browser decodes the HTML
attribute before parsing the JS string literal — so a malicious
placement_tags key like `x'),alert(1)//` breaks out of the JS
context. Reachable via importFile and validateDomain paths.

Rewrote the function with createElement + textContent and a
closure-based onclick (captures `tag` directly), matching the
pattern renderPlacements/renderCollections already use.

E2E (Playwright + Docker): seeded state.placementTags with the
attribute-bypass payload + an HTML-injection payload — no dialog,
no injected <img>, payloads visible as text, delete button still
removes via closure.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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