Skip to content

Update duplicate placement to track and switch source mesh and highlight chained duplicates#575

Merged
tracygardner merged 5 commits intomainfrom
codex/investigate-duplicate-tool-selection-issue
Apr 27, 2026
Merged

Update duplicate placement to track and switch source mesh and highlight chained duplicates#575
tracygardner merged 5 commits intomainfrom
codex/investigate-duplicate-tool-selection-issue

Conversation

@tracygardner
Copy link
Copy Markdown
Contributor

@tracygardner tracygardner commented Apr 27, 2026

Motivation

  • Allow users to continue duplicating newly created blocks by updating the source mesh and visual cues when chaining duplicates in the scene.

Description

  • Change meshToClone from const to let so it can be updated when the duplicate chain source changes.
  • Add updateDuplicateChainSource which highlights the new block, updates blockId, sets the crosshair cursor, resolves the new source mesh (with an animation-frame retry loop), and updates meshToClone visibility and bounding box.
  • Replace direct highlightBlockById calls after duplicateBlockAndInsert with updateDuplicateChainSource(newBlock, workspace) in both click and keyboard placement paths.
  • Ensure the previous source mesh bounding box is hidden when switching to a new source, and handle cases where the source mesh is not immediately available by retrying up to a limit.

Testing

  • Ran unit and integration tests via npm test, which completed successfully.
  • Ran linting via npm run lint, which passed without errors.

Codex Task

Summary by CodeRabbit

  • Bug Fixes
    • Fixed duplicate element workflow to properly synchronize visual highlighting and element visibility across duplicate chains, ensuring correct visual feedback when placing duplicates through click or keyboard methods.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@tracygardner has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 40 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bb7d6b1-cc0b-4eb4-bf84-b0e1a8c7e630

📥 Commits

Reviewing files that changed from the base of the PR and between 5592879 and 4366d1e.

📒 Files selected for processing (1)
  • ui/gizmos.js
📝 Walkthrough

Walkthrough

The change introduces updateDuplicateChainSource function to synchronize Blockly highlighting, block IDs, and mesh bounding-box visibility when duplicating blocks across a chain. meshToClone is changed from const to let to enable reassignment. Both click and keyboard placement paths now call this new function instead of only updating block highlights.

Changes

Cohort / File(s) Summary
Duplicate Chain Source Synchronization
ui/gizmos.js
Introduces updateDuplicateChainSource function to synchronize block highlighting, update blockId, resolve meshes via block-key lookup with deferred retries using requestAnimationFrame, and manage bounding-box visibility by hiding the previous mesh's bounding box and showing the newly resolved mesh's. Integrates this function into both click and keyboard placement paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • Keyboard controls for Duplicate gizmo #544: Adds keyboard/canvas placement and bounding-box showing behavior to the duplicate-placement flow; this PR complements it by introducing updateDuplicateChainSource to synchronize highlighting and mesh updates throughout the duplicate chain.

Poem

🐰 A rabbit hops through blocks divine,
Duplicates aligned in perfect line,
Highlights dance, and meshes glow,
Bounding boxes steal the show!
Chain by chain, they're synchronized with care. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: updating duplicate placement to track/switch source mesh and highlight chained duplicates, which aligns with the PR's core objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/investigate-duplicate-tool-selection-issue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 27, 2026

Deploying flockxr with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2ac4395
Status: ✅  Deploy successful!
Preview URL: https://05d27bad.flockxr.pages.dev
Branch Preview URL: https://codex-investigate-duplicate.flockxr.pages.dev

View logs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
ui/gizmos.js (1)

999-1000: Nit: || newBlock.id fallback is dead.

getMeshFromBlockKey looks up by mesh.metadata.blockKey, not by Blockly block id, so substituting newBlock.id when getBlockKeyFromBlock returns null cannot match any entry in blockKeyToMeshes. The real fallback is || getMeshFromBlock(newBlock) on the next line. The || newBlock.id only avoids passing null into getMeshFromBlockKey (which already early-returns on a falsy key), so it is effectively a no-op.

-      const newBlockKey = getBlockKeyFromBlock(newBlock) || newBlock.id;
-      let nextSource = getMeshFromBlockKey(newBlockKey) || getMeshFromBlock(newBlock);
+      const newBlockKey = getBlockKeyFromBlock(newBlock);
+      let nextSource =
+        (newBlockKey && getMeshFromBlockKey(newBlockKey)) ||
+        getMeshFromBlock(newBlock);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/gizmos.js` around lines 999 - 1000, The fallback "|| newBlock.id" is dead
because getMeshFromBlockKey looks up mesh.metadata.blockKey (not Blockly block
ids), so supplying newBlock.id never yields a match; remove the "|| newBlock.id"
and instead rely on the existing fallback to getMeshFromBlock(newBlock). Update
the code around getBlockKeyFromBlock(newBlock) and
getMeshFromBlockKey/getMeshFromBlock so you pass the real blockKey or null and
let getMeshFromBlock(newBlock) run as the true fallback; reference
getBlockKeyFromBlock, getMeshFromBlockKey, getMeshFromBlock and blockKeyToMeshes
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/gizmos.js`:
- Around line 988-1020: The retry loop in updateDuplicateChainSource uses
requestAnimationFrame without cancelling or guarding, so late callbacks can
mutate meshToClone after duplicate mode exits; modify resolveSourceMesh to store
the RAF handle (e.g., let duplicateRafId) when calling requestAnimationFrame,
guard inside resolveSourceMesh with a check that duplicate mode is still active
(e.g., isDuplicateModeActive() or a duplicateMode flag) before touching
meshToClone, and ensure you cancel any pending RAF via
cancelAnimationFrame(duplicateRafId) from the explicit exit paths (callers like
exitGizmoState(), your click-out/cancel handlers and any toggle handlers) so
pending callbacks are cleared when duplicate mode ends. Ensure the same
meshToClone checks (meshToClone && meshToClone !== nextSource) remain inside the
guarded block.
- Around line 1011-1016: When swapping meshToClone to nextSource, also restore
the previous source's visibility like
resetBoundingBoxVisibilityIfManuallyChanged does: before assigning meshToClone =
nextSource, if meshToClone exists and is not nextSource set
meshToClone.showBoundingBox = false and restore its visibility (e.g. set
meshToClone.visibility back to 1 or its original visible value) so intermediate
sources aren't left at 0.001; update the swap logic around
meshToClone/nextSource and keep references to
gizmoManager.attachedMesh/resetBoundingBoxVisibilityIfManuallyChanged semantics.

---

Nitpick comments:
In `@ui/gizmos.js`:
- Around line 999-1000: The fallback "|| newBlock.id" is dead because
getMeshFromBlockKey looks up mesh.metadata.blockKey (not Blockly block ids), so
supplying newBlock.id never yields a match; remove the "|| newBlock.id" and
instead rely on the existing fallback to getMeshFromBlock(newBlock). Update the
code around getBlockKeyFromBlock(newBlock) and
getMeshFromBlockKey/getMeshFromBlock so you pass the real blockKey or null and
let getMeshFromBlock(newBlock) run as the true fallback; reference
getBlockKeyFromBlock, getMeshFromBlockKey, getMeshFromBlock and blockKeyToMeshes
when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1c0b407-49b9-457a-a2ca-7d7961ea6ed2

📥 Commits

Reviewing files that changed from the base of the PR and between 9c0b398 and 5592879.

📒 Files selected for processing (1)
  • ui/gizmos.js

Comment thread ui/gizmos.js
Comment thread ui/gizmos.js Outdated
Comment on lines +1011 to +1016
if (meshToClone && meshToClone !== nextSource) {
meshToClone.showBoundingBox = false;
}
meshToClone = nextSource;
meshToClone.visibility = 0.001;
meshToClone.showBoundingBox = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm there is no other place that walks the chain to restore visibility
# on intermediate meshes after a chained duplicate.
rg -nP -C3 '\bvisibility\s*=\s*(0|1|0\.001)\b' --type=js -g '!**/node_modules/**'

Repository: flipcomputing/flock

Length of output: 50376


🏁 Script executed:

# Find all call sites of resetBoundingBoxVisibilityIfManuallyChanged
rg -n 'resetBoundingBoxVisibilityIfManuallyChanged' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 207


🏁 Script executed:

# Check context around line 1736
sed -n '1720,1750p' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 1024


🏁 Script executed:

# Check context around lines 1011-1016 (the chain switch logic)
sed -n '990,1025p' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 1090


🏁 Script executed:

# Search for where duplicate mode is activated/deactivated and any cleanup
rg -n -B2 -A2 'activeDuplicatePickHandler|toggleDuplicate|toggleGizmo' ui/gizmos.js | head -100

Repository: flipcomputing/flock

Length of output: 1977


🏁 Script executed:

# Find exitGizmoState function and see what cleanup it does
rg -n -A15 'function exitGizmoState|const exitGizmoState' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 524


🏁 Script executed:

# Find runCleanups function and what cleanup functions are queued
rg -n -B5 -A10 'function runCleanups|const runCleanups|cleanups\.push' ui/gizmos.js | head -80

Repository: flipcomputing/flock

Length of output: 532


🏁 Script executed:

# Find where onExit is called during duplicate mode setup (search around line 1068)
sed -n '1050,1120p' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 2117


🏁 Script executed:

# Search for where onExit is called during duplicate mode (if at all)
rg -n 'onExit\(' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 466


🏁 Script executed:

# Find updateDuplicateChainSource which is called after each placement
rg -n -A20 'function updateDuplicateChainSource' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 45


🏁 Script executed:

# Search for updateDuplicateChainSource differently
rg -n 'updateDuplicateChainSource' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 255


🏁 Script executed:

# Show the updateDuplicateChainSource function
sed -n '988,1010p' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 756


🏁 Script executed:

# Search for all references to meshToClone to understand the full flow
rg -n 'meshToClone' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 583


🏁 Script executed:

# Check what happens at lines 1027, 1052, 1085 where showBoundingBox is set to false
sed -n '1025,1030p' ui/gizmos.js
sed -n '1050,1055p' ui/gizmos.js
sed -n '1083,1088p' ui/gizmos.js

Repository: flipcomputing/flock

Length of output: 680


Reset visibility of previous source when swapping meshToClone in the duplicate chain.

Each successful duplicate placement sets nextSource.visibility = 0.001 (line 1015). When the source switches to the next placement, only showBoundingBox is cleared on the previous source (line 1012); its visibility remains at 0.001. After a chain of N placements, every intermediate source mesh is left invisible at 0.001 visibility. resetBoundingBoxVisibilityIfManuallyChanged only restores the final gizmoManager.attachedMesh, leaving intermediates unreset. For non-composite primitives where the root is the visible geometry, this makes intermediate duplicates effectively invisible.

Restore visibility when swapping meshToClone to match the logic in resetBoundingBoxVisibilityIfManuallyChanged:

Suggested change
      if (meshToClone && meshToClone !== nextSource) {
        meshToClone.showBoundingBox = false;
+       // matches resetBoundingBoxVisibilityIfManuallyChanged
+       if (meshToClone.visibility === 0.001) meshToClone.visibility = 0;
      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/gizmos.js` around lines 1011 - 1016, When swapping meshToClone to
nextSource, also restore the previous source's visibility like
resetBoundingBoxVisibilityIfManuallyChanged does: before assigning meshToClone =
nextSource, if meshToClone exists and is not nextSource set
meshToClone.showBoundingBox = false and restore its visibility (e.g. set
meshToClone.visibility back to 1 or its original visible value) so intermediate
sources aren't left at 0.001; update the swap logic around
meshToClone/nextSource and keep references to
gizmoManager.attachedMesh/resetBoundingBoxVisibilityIfManuallyChanged semantics.

@tracygardner tracygardner merged commit 595b03f into main Apr 27, 2026
8 checks passed
@tracygardner tracygardner deleted the codex/investigate-duplicate-tool-selection-issue branch April 27, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant