fix: resolve duplicate members and missing leader actions on chapter pages#666
Merged
Conversation
…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
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 ', 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Root Cause
Bug 1 - Duplicate Members: SQL queries in
working-group-db.tsusedLEFT JOIN organization_membershipswhich 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/configendpoint wasn't returning the user'sidfield. The frontend checkscurrentUser.id === leader.user_id, butcurrentUser.idwas alwaysundefined.Changes
server/src/db/working-group-db.ts: Changed plainLEFT JOIN organization_membershipstoLEFT JOIN LATERAL ... LIMIT 1in three queries:getMembershipsByWorkingGroup()getLeaders()getLeadersBatch()server/src/http.ts: Addedid: req.user.idto the/api/configresponseTest plan
🤖 Generated with Claude Code