Skip to content

Decouple parent/child lifecycle + flatten block positioning#173

Merged
dconnenc merged 7 commits into
mainfrom
block-lifecycle-decouple
May 27, 2026
Merged

Decouple parent/child lifecycle + flatten block positioning#173
dconnenc merged 7 commits into
mainfrom
block-lifecycle-decouple

Conversation

@dconnenc

@dconnenc dconnenc commented May 27, 2026

Copy link
Copy Markdown
Owner

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_blocks surface 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)

  • Schema migration drops the position_scope virtual 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_blocks returns every block ordered by position (children remain serialized inline on parents via dag_metadata for FamilyFeudManager).
  • BlockSidebar rewritten as a single flat ordered list with one drop zone. Child blocks show an indent + "↳ from " label.
  • New detach_from_parent endpoint + 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_id mechanism, which queries ParticipantSubmissions.for_user across all blocks regardless of nesting.

Test plan

  • Backend specs pass (bundle exec rspec spec/services spec/controllers spec/models) — 266 examples, 0 failures locally.
  • CI system specs pass (local Cuprite was white-screening even on baseline, didn't validate locally).
  • In /manage with a fresh FamilyFeud: open question 1 alone → participants see question 1 → close it → FamilyFeud parent stays hidden throughout.
  • Drag a child question above its FamilyFeud parent in the sidebar → reordering persists.
  • Select a child → Detach button visible → click → block becomes top-level.
  • Open FamilyFeud → Start Playing → snapshot picks up responses collected from question children that were independently closed earlier.
  • Closing the FamilyFeud parent does NOT close its question children.

🤖 Generated with Claude Code

Dillon Cassidy and others added 2 commits May 26, 2026 11:48
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>
Dillon Cassidy and others added 5 commits May 27, 2026 13:49
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>
@dconnenc
dconnenc merged commit 83ce24d into main May 27, 2026
10 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.

1 participant