Decouple parent/child lifecycle + flatten block positioning#173
Merged
Conversation
Allow FamilyFeud's question children to be broadcast independently of the FamilyFeud parent. Each block now owns its status: open!/close!/ hide! no longer cascade to descendants, and visibility surfaces an open child even when its parent is hidden. The /manage Present and Stop-Presenting flows act on the selected block directly rather than routing through its parent. This lets a host broadcast each FamilyFeud question earlier in the run to collect responses, then later open the FamilyFeud and play it from the snapshot. GuessWho can already reference any block by id via its payload, so it can consume the same question blocks unchanged. Backend - ExperienceBlock#open!/close!/hide!: update only self. Removed all_descendant_ids (no longer used). - Visibility#monitor_visible_blocks + #participant_visible_blocks: include children that are independently open while their parent is not open. Helper participant_facing_blocks shared between participant and profile flows. - Visibility#participant_block_active? + #responded_participant_ids: consider all open blocks (not just parents), so question children count. Frontend - useBlockPresentation: handlePresent now closes any other open block (parent or child) and opens the target; handleStopPresenting closes the target; handlePlayNext closes current and opens next without routing through parents. findParentBlock helper removed. Tests - experience_block_spec: open!/close!/hide! cases assert children retain their status. - orchestrator_spec: open/close/hide_block! cases match new behavior. - orchestrator_spec: new coverage for start_family_feud_playing! snapshotting children's submissions after they were independently closed. - visibility_spec: new monitor + participant cases asserting open child surfaces when parent is hidden. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /manage sidebar can now reflect the actual broadcast sequence: positions are scoped to the experience, not to a parent. A FamilyFeud question can be sequenced anywhere in the program, even between two unrelated top-level blocks, while still showing its relationship to the FamilyFeud parent through a visual indent and label. Schema - Drop the position_scope virtual column (was COALESCE(parent_block_id, experience_id)) and its composite index. - Add an (experience_id, position) index. - Backfill: re-number all blocks per experience so each parent's children land directly after their parent. Preserves visual order on first load. Backend - Visibility#admin_visible_blocks returns every block ordered by position (was: top-level only). Children remain serialized inline on parents via dag_metadata for FamilyFeudManager's convenience. - Orchestrator#add_block!, #add_block_with_dependencies!, #insert_block_column! work against the experience-wide max_position. - Orchestrator#reorder_block! reorders within the experience's flat list rather than within sibling groups. - New Orchestrator#detach_block_from_parent! nulls parent_block_id and removes the depends_on link, promoting a child to top-level. - New POST /api/experiences/:id/blocks/:id/detach_from_parent route. Frontend - BlockSidebar rewritten: single flat ordered list, single drop zone, child blocks shown indented with a "↳ from <Parent>" label. - New useDetachBlockFromParent hook. - BlockDetailPanel offers a Detach affordance when the selected block has a parent. - ManageViewer's flattenedBlocks now maps directly from the flat list. Tests - experience_block_spec, orchestrator_spec, visibility_spec updated to reflect flat positioning and detach behavior. - 266 backend specs pass; tsc clean. System specs not validated locally — local Cuprite environment is white-screening even on baseline (unrelated to this branch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A child block now reads as: [grip] #1 Question 🔗 family feud Previously the relationship was only conveyed by indent + a small "↳ from X" caption that was easy to miss and got clipped by the row's fixed height. The fixed height becomes min-height so the second line can show, and the parent label now uses the same uppercase treatment as the kind label so it reads as a proper subtitle. The redundant inline arrow icon next to the kind label is removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sidebar's parent-relationship subtitle relied on block.parent_block_id which was declared in the TS types but never actually emitted by the backend serializer. Only the link-derived parent_block_ids (plural) was sent. As a result the "↳ family feud" subtitle never rendered. Serialize the column directly so the field matches the existing type contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # app/controllers/api/experience_blocks_controller.rb # app/frontend/Pages/Manage/Viewer/BlockDetailPanel.tsx # app/frontend/Pages/Manage/Viewer/ManageViewer.tsx # app/services/experiences/orchestrator.rb # app/services/experiences/visibility.rb # db/schema.rb
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Children render as top-level entries after the sidebar flatten, so the
children-{parentId} droppable they targeted no longer exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <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
Two related changes that together let a host broadcast FamilyFeud's question children independently of the FamilyFeud parent, with the sidebar reflecting the real broadcast order.
1. Decouple lifecycle (commit aa7e61c)
ExperienceBlock#open!/close!/hide!no longer cascade to descendants. Each block owns its own status.Visibility#monitor_visible_blocks+#participant_visible_blockssurface an open child even when its parent is hidden, so a question can be broadcast independently.useBlockPresentation(handlePresent / handleStopPresenting / handlePlayNext) acts on the selected block directly rather than routing through its parent.2. Flatten block positioning (commit baa3130)
position_scopevirtual column. Positions are now experience-wide; a child can be sequenced anywhere in the program. Backfill preserves visual order on first load.Visibility#admin_visible_blocksreturns every block ordered by position (children remain serialized inline on parents viadag_metadatafor FamilyFeudManager).BlockSidebarrewritten as a single flat ordered list with one drop zone. Child blocks show an indent + "↳ from " label.detach_from_parentendpoint + UI button so a child can be promoted to top-level.Notes for review
This is the smaller-blast-radius alternative to the parked Source/Consumer refactor. The intent (per Jared's suggestion) is to try parent/child + ordering first and only revisit the bigger schema change if this turns out to be limiting. The remaining requirement from the original ask — GuessWho consuming FamilyFeud questions — is already satisfied by the existing
slides[].block_idmechanism, which queriesParticipantSubmissions.for_useracross all blocks regardless of nesting.Test plan
bundle exec rspec spec/services spec/controllers spec/models) — 266 examples, 0 failures locally.🤖 Generated with Claude Code