Repository: microsoft/qsharp
Pull request #2330 (2025-04-23 20:12:05)

Support Enter and Escape Keys with Prompts
- Enables the use of the "Enter" key to affirm input and the "Escape" key to cancel input in argument prompt.
- Enables the use of the "Enter" key to confirm and the "Escape" key to cancel on remove-qubit confirmation prompt.
- Automatically replaces "pi" with the symbol "π" in user input for arguments for better readability.


Patch:
diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs
index af0685d5..7d1c3188 100644
--- a/compiler/qsc_circuit/src/builder.rs
+++ b/compiler/qsc_circuit/src/builder.rs
@@ -264,10 +264,8 @@ impl Builder {
             });
         }
 
-        let max_q_id = qubits.len() - 1;
-
         Circuit {
-            component_grid: operation_list_to_grid(operations, max_q_id),
+            component_grid: operation_list_to_grid(operations, qubits.len()),
             qubits,
         }
     }
diff --git a/compiler/qsc_circuit/src/circuit.rs b/compiler/qsc_circuit/src/circuit.rs
index 5407dc70..a42c6352 100644
--- a/compiler/qsc_circuit/src/circuit.rs
+++ b/compiler/qsc_circuit/src/circuit.rs
@@ -691,12 +691,12 @@ fn get_row_indexes(
 /// # Arguments
 ///
 /// * `operations` - A vector of operations to be converted.
-/// * `max_q_id` - The maximum qubit ID.
+/// * `num_qubits` - The number of qubits in the circuit.
 ///
 /// # Returns
 ///
 /// A component grid representing the operations.
-pub fn operation_list_to_grid(mut operations: Vec<Operation>, max_q_id: usize) -> ComponentGrid {
+pub fn operation_list_to_grid(mut operations: Vec<Operation>, num_qubits: usize) -> ComponentGrid {
     for op in &mut operations {
         // The children data structure is a grid, so checking if it is
         // length 1 is actually checking if it has a single column,
@@ -707,13 +707,16 @@ pub fn operation_list_to_grid(mut operations: Vec<Operation>, max_q_id: usize) -
         if op.children().len() == 1 {
             match op {
                 Operation::Measurement(m) => {
-                    m.children = operation_list_to_grid(m.children.remove(0).components, max_q_id);
+                    m.children =
+                        operation_list_to_grid(m.children.remove(0).components, num_qubits);
                 }
                 Operation::Unitary(u) => {
-                    u.children = operation_list_to_grid(u.children.remove(0).components, max_q_id);
+                    u.children =
+                        operation_list_to_grid(u.children.remove(0).components, num_qubits);
                 }
                 Operation::Ket(k) => {
-                    k.children = operation_list_to_grid(k.children.remove(0).components, max_q_id);
+                    k.children =
+                        operation_list_to_grid(k.children.remove(0).components, num_qubits);
                 }
             }
         }
@@ -721,7 +724,7 @@ pub fn operation_list_to_grid(mut operations: Vec<Operation>, max_q_id: usize) -
 
     // Convert the operations into a component grid
     let mut component_grid = vec![];
-    for col in remove_padding(operation_list_to_padded_array(operations, max_q_id)) {
+    for col in remove_padding(operation_list_to_padded_array(operations, num_qubits)) {
         let column = ComponentColumn { components: col };
         component_grid.push(column);
     }
@@ -733,20 +736,20 @@ pub fn operation_list_to_grid(mut operations: Vec<Operation>, max_q_id: usize) -
 /// # Arguments
 ///
 /// * `operations` - A vector of operations to be converted.
-/// * `max_q_id` - The maximum qubit ID.
+/// * `num_qubits` - The number of qubits in the circuit.
 ///
 /// # Returns
 ///
 /// A 2D vector of optional operations padded with `None`.
 fn operation_list_to_padded_array(
     operations: Vec<Operation>,
-    max_q_id: usize,
+    num_qubits: usize,
 ) -> Vec<Vec<Option<Operation>>> {
     if operations.is_empty() {
         return vec![];
     }
 
-    let grouped_ops = group_operations(&operations, max_q_id);
+    let grouped_ops = group_operations(&operations, num_qubits);
     let aligned_ops = transform_to_col_row(align_ops(grouped_ops));
 
     // Need to convert to optional operations so we can
@@ -815,14 +818,19 @@ fn transform_to_col_row(aligned_ops: Vec<Vec<Option<usize>>>) -> Vec<Vec<Option<
 /// # Arguments
 ///
 /// * `operations` - A slice of operations to be grouped.
-/// * `max_q_id` - The maximum qubit ID.
+/// * `num_qubits` - The number of qubits in the circuit.
 ///
 /// # Returns
 ///
 /// A 2D vector of indices where `groupedOps[i][j]` is the index of the operations
 /// at register `i` and column `j` (not yet aligned/padded).
-fn group_operations(operations: &[Operation], max_q_id: usize) -> Vec<Vec<usize>> {
-    let mut grouped_ops = vec![vec![]; max_q_id + 1]; // Add one so that it is an "end" index
+fn group_operations(operations: &[Operation], num_qubits: usize) -> Vec<Vec<usize>> {
+    let mut grouped_ops = vec![vec![]; num_qubits];
+
+    let max_q_id = match num_qubits {
+        0 => 0,
+        _ => num_qubits - 1,
+    };
 
     for (instr_idx, op) in operations.iter().enumerate() {
         let ctrls = match op {
@@ -848,21 +856,16 @@ fn group_operations(operations: &[Operation], max_q_id: usize) -> Vec<Vec<usize>
             continue;
         }
 
-        let min_reg_idx = if is_classically_controlled {
-            0
-        } else {
-            match q_reg_idx_list.iter().min() {
-                Some(min_reg_idx) => *min_reg_idx,
-                None => 0,
-            }
-        };
-        let max_reg_idx = if is_classically_controlled {
-            max_q_id
+        let (min_reg_idx, max_reg_idx) = if is_classically_controlled {
+            (0, max_q_id)
         } else {
-            match q_reg_idx_list.iter().max() {
-                Some(max_reg_idx) => *max_reg_idx,
-                None => max_q_id,
-            }
+            q_reg_idx_list
+                .into_iter()
+                .fold(None, |acc, x| match acc {
+                    None => Some((x, x)),
+                    Some((min, max)) => Some((min.min(x), max.max(x))),
+                })
+                .unwrap_or((0, max_q_id))
         };
 
         for reg_ops in grouped_ops
diff --git a/compiler/qsc_circuit/src/circuit_to_qsharp.rs b/compiler/qsc_circuit/src/circuit_to_qsharp.rs
index 18461ece..5d717acc 100644
--- a/compiler/qsc_circuit/src/circuit_to_qsharp.rs
+++ b/compiler/qsc_circuit/src/circuit_to_qsharp.rs
@@ -80,12 +80,19 @@ fn build_operation_def(circuit_name: &str, circuit: &Circuit) -> String {
     let mut measure_results = vec![];
     let indent = "    ".repeat(indentation_level);
 
-    // Add an assert for the number of qubits
-    qsharp_str.push_str(&generate_qubit_validation(
-        circuit_name,
-        &qubits,
-        indentation_level,
-    ));
+    // If there are operation, add an assert for the number of qubits
+    if !circuit.component_grid.is_empty()
+        && circuit
+            .component_grid
+            .iter()
+            .any(|col| !col.components.is_empty())
+    {
+        qsharp_str.push_str(&generate_qubit_validation(
+            circuit_name,
+            &qubits,
+            indentation_level,
+        ));
+    }
 
     let mut body_str = String::new();
     let mut should_add_pi = false;
@@ -113,12 +120,10 @@ fn build_operation_def(circuit_name: &str, circuit: &Circuit) -> String {
                 }
             }
 
-            // Look for a "π" or "pi" in the args
+            // Look for a "π" in the args
             let args = op.args();
             if !should_add_pi && !args.is_empty() {
-                should_add_pi = args
-                    .iter()
-                    .any(|arg| arg.contains("π") || arg.to_lowercase().contains("pi"));
+                should_add_pi = args.iter().any(|arg| arg.contains("π"));
             }
         }
     }
@@ -230,24 +235,15 @@ fn generate_unitary_call(
 }
 
 fn generate_ket_call(ket: &Ket, qubits: &FxHashMap<usize, String>, indent: &str) -> String {
-    if ket.gate == "1" {
-        // Note "|1〉" will generate two operations: Reset and X
-        let ket_str = ket_call(ket, qubits);
-        let mut call_str = format!("{indent}{ket_str};\n");
-        let op_x = Unitary {
-            gate: "X".to_string(),
-            is_adjoint: false,
-            controls: vec![],
-            targets: ket.targets.clone(),
-            args: vec![],
-            children: vec![],
-        };
-        let operation_str = operation_call(&op_x, qubits);
-        writeln!(call_str, "{indent}{operation_str};").expect("could not write to call_str");
-        call_str
-    } else {
+    // Note: The only supported ket operation is "0"
+    if ket.gate == "0" {
         let ket_str = ket_call(ket, qubits);
         format!("{indent}{ket_str};\n")
+    } else {
+        format!(
+            "{indent}fail \"Unsupported ket operation: |{}〉\";\n",
+            ket.gate
+        )
     }
 }
 
@@ -300,6 +296,7 @@ fn measurement_call(measurement: &Measurement, qubits: &FxHashMap<usize, String>
 }
 
 fn ket_call(ket: &Ket, qubits: &FxHashMap<usize, String>) -> String {
+    // Note: The only supported ket operation is "0" which is a reset operation
     let targets = ket
         .targets
         .iter()
@@ -328,8 +325,6 @@ fn operation_call(unitary: &Unitary, qubits: &FxHashMap<usize, String>) -> Strin
 
     // Create the regex for matching numbers (both integers and doubles)
     let number_regex = Regex::new(r"((\d+(\.\d*)?)|(\.\d+))").expect("Regex should compile");
-    // Create the regex for matching "pi" (case-insensitive)
-    let pi_regex = Regex::new(r"(?i)pi").expect("Regex should compile");
 
     // Convert ints to doubles by appending a `.` to the end of the integer
     for arg in &unitary.args {
@@ -345,9 +340,6 @@ fn operation_call(unitary: &Unitary, qubits: &FxHashMap<usize, String>) -> Strin
             })
             .to_string();
 
-        // Replace "pi" (case-insensitive) with "π"
-        let updated_arg = pi_regex.replace_all(&updated_arg, "π").to_string();
-
         args.push(updated_arg);
     }
 
diff --git a/compiler/qsc_qasm/src/semantic/const_eval.rs b/compiler/qsc_qasm/src/semantic/const_eval.rs
index d43931dd..25fd08ac 100644
--- a/compiler/qsc_qasm/src/semantic/const_eval.rs
+++ b/compiler/qsc_qasm/src/semantic/const_eval.rs
@@ -11,6 +11,7 @@ use super::ast::{
     UnaryOp, UnaryOpExpr,
 };
 use super::symbols::SymbolId;
+use crate::semantic::types::binary_op_is_supported_for_types;
 use crate::semantic::Lowerer;
 use crate::stdlib::angle;
 use crate::{
@@ -25,18 +26,19 @@ use thiserror::Error;
 #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)]
 pub enum ConstEvalError {
     #[error("expression must be const")]
-    #[diagnostic(code("Qasm.Compiler.ExprMustBeConst"))]
+    #[diagnostic(code("Qasm.Lowerer.ExprMustBeConst"))]
     ExprMustBeConst(#[label] Span),
     #[error("uint expression must evaluate to a non-negative value, but it evaluated to {0}")]
-    #[diagnostic(code("Qasm.Compiler.NegativeUIntValue"))]
+    #[diagnostic(code("Qasm.Lowerer.NegativeUIntValue"))]
     NegativeUIntValue(i64, #[label] Span),
     #[error("{0} doesn't fit in {1}")]
-    #[diagnostic(code("Qasm.Compiler.ValueOverflow"))]
+    #[diagnostic(code("Qasm.Lowerer.ValueOverflow"))]
     ValueOverflow(String, String, #[label] Span),
+    #[error("{0} is not supported between types {1} and {2}")]
+    #[diagnostic(code("Qasm.Lowerer.UnsupportedBinaryOp"))]
+    UnsupportedBinaryOp(String, String, String, #[label] Span),
 }
 
-impl ConstEvalError {}
-
 impl Expr {
     /// Tries to evaluate the expression. It takes the current `Lowerer` as
     /// the evaluation context to resolve symbols and push errors in case
@@ -133,30 +135,6 @@ impl UnaryOpExpr {
     }
 }
 
-/// By this point it is guaranteed that the lhs and rhs are of the same type.
-/// Any conversions have been made explicit by inserting casts during lowering.
-/// Note: the type of the binary expression doesn't need to be the same as the
-///       operands, for example, comparison operators can have integer operands
-///       but their type is boolean.
-/// We can write a simpler implementation under that assumption.
-///
-/// There are some exceptions:
-///  1. The rhs in Shl and Shr must be of type `UInt`.
-///  2. Angle can be multiplied and divided by `UInt`.
-fn assert_binary_op_ty_invariant(op: BinOp, lhs_ty: &Type, rhs_ty: &Type) {
-    // Exceptions:
-    if matches!(
-        (op, lhs_ty, rhs_ty),
-        (BinOp::Shl | BinOp::Shr, _, _)
-            | (BinOp::Mul | BinOp::Div, Type::Angle(..), Type::UInt(..))
-            | (BinOp::Mul, Type::UInt(..), Type::Angle(..))
-    ) {
-        return;
-    }
-
-    assert_eq!(lhs_ty, rhs_ty);
-}
-
 impl BinaryOpExpr {
     #[allow(clippy::too_many_lines)]
     fn const_eval(&self, ctx: &mut Lowerer) -> Option<LiteralKind> {
@@ -167,7 +145,15 @@ impl BinaryOpExpr {
         let (lhs, rhs) = (lhs?, rhs?);
         let lhs_ty = &self.lhs.ty;
 
-        assert_binary_op_ty_invariant(self.op, &self.lhs.ty, &self.rhs.ty);
+        if !binary_op_is_supported_for_types(self.op, &self.lhs.ty, &self.rhs.ty) {
+            ctx.push_const_eval_error(ConstEvalError::UnsupportedBinaryOp(
+                self.op.to_string(),
+                self.lhs.ty.to_string(),
+                self.rhs.ty.to_string(),
+                self.span(),
+            ));
+            return None;
+        }
 
         match &self.op {
             // Bit Shifts
diff --git a/compiler/qsc_qasm/src/semantic/types.rs b/compiler/qsc_qasm/src/semantic/types.rs
index 73733382..cf59e25c 100644
--- a/compiler/qsc_qasm/src/semantic/types.rs
+++ b/compiler/qsc_qasm/src/semantic/types.rs
@@ -8,7 +8,7 @@ use std::fmt::{Display, Formatter};
 
 use crate::parser::ast as syntax;
 
-use super::ast::LiteralKind;
+use super::ast::{BinOp, LiteralKind};
 
 #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
 pub enum Type {
@@ -789,3 +789,62 @@ pub(crate) fn is_complex_binop_supported(op: syntax::BinOp) -> bool {
             | syntax::BinOp::Exp
     )
 }
+
+/// Returns true if the binary op is supported for the `lhs` and `rhs` types.
+/// Any conversions have been made explicit by inserting casts during lowering.
+pub(crate) fn binary_op_is_supported_for_types(op: BinOp, lhs_ty: &Type, rhs_ty: &Type) -> bool {
+    use Type::*;
+
+    match op {
+        // Bit Shifts: `rhs_ty` must always be `uint`.
+        BinOp::Shl | BinOp::Shr => {
+            matches!(lhs_ty, UInt(..) | Angle(..) | Bit(..) | BitArray(..))
+                && matches!(rhs_ty, UInt(..))
+        }
+
+        // Bitwise.
+        BinOp::AndB | BinOp::OrB | BinOp::XorB => {
+            base_types_equal(lhs_ty, rhs_ty)
+                && matches!(lhs_ty, UInt(..) | Angle(..) | Bit(..) | BitArray(..))
+        }
+
+        // Logical.
+        BinOp::AndL | BinOp::OrL => matches!(lhs_ty, Bool(..)) && matches!(rhs_ty, Bool(..)),
+
+        // Comparison.
+        BinOp::Eq | BinOp::Neq | BinOp::Gt | BinOp::Gte | BinOp::Lt | BinOp::Lte => {
+            base_types_equal(lhs_ty, rhs_ty)
+                && matches!(
+                    lhs_ty,
+                    Int(..) | UInt(..) | Angle(..) | Bit(..) | BitArray(..)
+                )
+        }
+
+        // Arithmetic
+        BinOp::Add | BinOp::Sub => {
+            base_types_equal(lhs_ty, rhs_ty)
+                && matches!(lhs_ty, Int(..) | UInt(..) | Float(..) | Angle(..))
+        }
+        BinOp::Mul => {
+            let uint_angle_exception = (matches!(lhs_ty, Angle(..)) && matches!(rhs_ty, UInt(..)))
+                || (matches!(lhs_ty, UInt(..)) && matches!(rhs_ty, Angle(..)));
+
+            let base_case = base_types_equal(lhs_ty, rhs_ty)
+                && matches!(lhs_ty, Int(..) | UInt(..) | Float(..));
+
+            uint_angle_exception || base_case
+        }
+        BinOp::Div => {
+            let uint_angle_exception = matches!(lhs_ty, Angle(..)) && matches!(rhs_ty, UInt(..));
+
+            let base_case = base_types_equal(lhs_ty, rhs_ty)
+                && matches!(lhs_ty, Int(..) | UInt(..) | Float(..) | Angle(..));
+
+            uint_angle_exception || base_case
+        }
+        BinOp::Mod => base_types_equal(lhs_ty, rhs_ty) && matches!(lhs_ty, Int(..) | UInt(..)),
+        BinOp::Exp => {
+            base_types_equal(lhs_ty, rhs_ty) && matches!(lhs_ty, Int(..) | UInt(..) | Float(..))
+        }
+    }
+}
diff --git a/library/std/src/Std/Convert.qs b/library/std/src/Std/Convert.qs
index 721dc94e..7af5d066 100644
--- a/library/std/src/Std/Convert.qs
+++ b/library/std/src/Std/Convert.qs
@@ -174,7 +174,7 @@ function BigIntAsBoolArray(number : BigInt, bits : Int) : Bool[] {
 function BigIntAsInt(number : BigInt) : Int {
     let max = 9_223_372_036_854_775_807L;
     let min = -9_223_372_036_854_775_808L;
-    Fact(number >= min and number <= max, $"`number`=number is too big to fit into Int.");
+    Fact(number >= min and number <= max, $"BigIntAsInt: {number} is too big to fit into Int.");
 
     mutable result = 0;
     mutable powL = 1L;
diff --git a/npm/qsharp/src/shared/register.ts b/npm/qsharp/src/shared/register.ts
index 0dcab75d..74b086d0 100644
--- a/npm/qsharp/src/shared/register.ts
+++ b/npm/qsharp/src/shared/register.ts
@@ -20,20 +20,20 @@ export interface Register {
 }
 
 /**
- * Metadata for qubit register.
+ * Rendering data for qubit register.
  */
-export interface RegisterMetadata {
+export interface RegisterRenderData {
   /** Type of register. */
   type: RegisterType;
   /** y coord of register */
   y: number;
   /** Nested classical registers attached to quantum register. */
-  children?: RegisterMetadata[];
+  children?: RegisterRenderData[];
 }
 
 /**
- * Mapping from qubit IDs to their register metadata.
+ * Mapping from qubit IDs to their register render data.
  */
 export interface RegisterMap {
-  [id: number]: RegisterMetadata;
+  [id: number]: RegisterRenderData;
 }
diff --git a/npm/qsharp/ux/circuit-vis/contextMenu.ts b/npm/qsharp/ux/circuit-vis/contextMenu.ts
index 2747d7e8..a7c2c7a4 100644
--- a/npm/qsharp/ux/circuit-vis/contextMenu.ts
+++ b/npm/qsharp/ux/circuit-vis/contextMenu.ts
@@ -238,7 +238,7 @@ const validateExpression = (input: string): boolean => {
     // Validate the remaining expression (without parentheses)
     const sign = "[+-]?";
     const number = "((\\d+(\\.\\d*)?))"; // Matches integers and decimals
-    const pi = "(?i:π|pi)"; // Matches π or pi
+    const pi = "(?i:π)"; // Matches π
     const value = `${sign}(${number}|${pi})`; // Matches a signed number or π
     const operator = "[+\\-*/]"; // Matches arithmetic operators
     const expressionRegex = new RegExp(
@@ -311,6 +311,39 @@ const _createInputPrompt = (
   const buttonsContainer = document.createElement("div");
   buttonsContainer.classList.add("prompt-buttons");
 
+  // Create the OK button
+  const okButton = document.createElement("button");
+  okButton.classList.add("prompt-button");
+  okButton.textContent = "OK";
+
+  // Function to replace "pi" with "π" (case-insensitive)
+  const replacePiWithSymbol = (input: string) => input.replace(/pi/gi, "π");
+
+  // Function to validate input and toggle the OK button
+  const validateAndToggleOkButton = () => {
+    const processedInput = replacePiWithSymbol(inputElem.value.trim());
+    const isValid = validateInput(processedInput);
+    okButton.disabled = !isValid;
+  };
+
+  // Add input event listener for validation
+  inputElem.addEventListener("input", validateAndToggleOkButton);
+
+  // Handle Enter key when input is focused
+  inputElem.addEventListener("keydown", (event) => {
+    if (event.key === "Enter" && !okButton.disabled) {
+      event.preventDefault();
+      okButton.click();
+    }
+  });
+
+  okButton.disabled = !validateInput(replacePiWithSymbol(defaultValue.trim()));
+  okButton.addEventListener("click", () => {
+    callback(replacePiWithSymbol(inputElem.value.trim()));
+    document.body.removeChild(overlay);
+    document.removeEventListener("keydown", handleGlobalKeyDown, true);
+  });
+
   // Create the π button
   const piButton = document.createElement("button");
   piButton.textContent = "π";
@@ -325,16 +358,6 @@ const _createInputPrompt = (
     validateAndToggleOkButton();
   });
 
-  // Create the OK button
-  const okButton = document.createElement("button");
-  okButton.classList.add("prompt-button");
-  okButton.textContent = "OK";
-  okButton.disabled = !validateInput(defaultValue);
-  okButton.addEventListener("click", () => {
-    callback(inputElem.value.trim());
-    document.body.removeChild(overlay);
-  });
-
   // Create the Cancel button
   const cancelButton = document.createElement("button");
   cancelButton.classList.add("prompt-button");
@@ -342,16 +365,17 @@ const _createInputPrompt = (
   cancelButton.addEventListener("click", () => {
     callback(null);
     document.body.removeChild(overlay);
+    document.removeEventListener("keydown", handleGlobalKeyDown, true);
   });
 
-  // Function to validate input and toggle the OK button
-  const validateAndToggleOkButton = () => {
-    const isValid = validateInput(inputElem.value.trim());
-    okButton.disabled = !isValid;
+  // Handle Escape key globally while prompt is open
+  const handleGlobalKeyDown = (event: KeyboardEvent) => {
+    if (event.key === "Escape") {
+      event.preventDefault();
+      cancelButton.click();
+    }
   };
-
-  // Add input event listener for validation
-  inputElem.addEventListener("input", validateAndToggleOkButton);
+  document.addEventListener("keydown", handleGlobalKeyDown, true);
 
   // Append buttons to the container
   buttonsContainer.appendChild(piButton);
diff --git a/npm/qsharp/ux/circuit-vis/draggable.ts b/npm/qsharp/ux/circuit-vis/draggable.ts
index 08a4af48..22717eec 100644
--- a/npm/qsharp/ux/circuit-vis/draggable.ts
+++ b/npm/qsharp/ux/circuit-vis/draggable.ts
@@ -6,7 +6,7 @@ import { ComponentGrid, Operation } from "./circuit";
 import { gatePadding, minGateWidth, startX } from "./constants";
 import { box, controlDot } from "./formatters/formatUtils";
 import { formatGate } from "./formatters/gateFormatter";
-import { toMetadata } from "./panel";
+import { toRenderData } from "./panel";
 import { Sqore } from "./sqore";
 import {
   findLocation,
@@ -60,8 +60,8 @@ const createGhostElement = (
   const ghost = isControl
     ? controlDot(20, 20)
     : (() => {
-        const ghostMetadata = toMetadata(selectedOperation, 0, 0);
-        return formatGate(ghostMetadata).cloneNode(true) as SVGElement;
+        const ghostRenderData = toRenderData(selectedOperation, 0, 0);
+        return formatGate(ghostRenderData).cloneNode(true) as SVGElement;
       })();
 
   // Generate svg element to wrap around ghost element
diff --git a/npm/qsharp/ux/circuit-vis/events.ts b/npm/qsharp/ux/circuit-vis/events.ts
index e9f83534..25172a84 100644
--- a/npm/qsharp/ux/circuit-vis/events.ts
+++ b/npm/qsharp/ux/circuit-vis/events.ts
@@ -1,8 +1,6 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
 
-import cloneDeep from "lodash/cloneDeep";
-import isEqual from "lodash/isEqual";
 import { ComponentGrid, Operation, Qubit, Unitary } from "./circuit";
 import { Sqore } from "./sqore";
 import { toolboxGateDictionary } from "./panel";
@@ -15,6 +13,7 @@ import {
   getWireData,
   locationStringToIndexes,
   findParentArray,
+  deepEqual,
 } from "./utils";
 import { addContextMenuToHostElem, promptForArguments } from "./contextMenu";
 import {
@@ -420,7 +419,10 @@ class CircuitEvents {
     dropzoneElems.forEach((dropzoneElem) => {
       dropzoneElem.addEventListener("mouseup", async (ev: MouseEvent) => {
         const copying = ev.ctrlKey;
-        const originalGrid = cloneDeep(this.componentGrid);
+        // Create a deep copy of the component grid
+        const originalGrid = JSON.parse(
+          JSON.stringify(this.componentGrid),
+        ) as ComponentGrid;
         const targetLoc = dropzoneElem.getAttribute("data-dropzone-location");
         const insertNewColumn =
           dropzoneElem.getAttribute("data-dropzone-inter-column") == "true" ||
@@ -512,8 +514,7 @@ class CircuitEvents {
         this.selectedOperation = null;
         this.movingControl = false;
 
-        if (isEqual(originalGrid, this.componentGrid) === false)
-          this.renderFn();
+        if (!deepEqual(originalGrid, this.componentGrid)) this.renderFn();
       });
     });
   }
@@ -789,6 +790,7 @@ const _createConfirmPrompt = (
   okButton.addEventListener("click", () => {
     callback(true); // User confirmed
     document.body.removeChild(overlay);
+    document.removeEventListener("keydown", handleGlobalKeyDown, true);
   });
 
   // Create the Cancel button
@@ -798,8 +800,21 @@ const _createConfirmPrompt = (
   cancelButton.addEventListener("click", () => {
     callback(false); // User canceled
     document.body.removeChild(overlay);
+    document.removeEventListener("keydown", handleGlobalKeyDown, true);
   });
 
+  // Handle Enter and Escape keys globally while prompt is open
+  const handleGlobalKeyDown = (event: KeyboardEvent) => {
+    if (event.key === "Enter") {
+      event.preventDefault();
+      okButton.click();
+    } else if (event.key === "Escape") {
+      event.preventDefault();
+      cancelButton.click();
+    }
+  };
+  document.addEventListener("keydown", handleGlobalKeyDown, true);
+
   buttonsContainer.appendChild(okButton);
   buttonsContainer.appendChild(cancelButton);
 
diff --git a/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts
index 5e999da5..01dd74a0 100644
--- a/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts
+++ b/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
 
-import { Metadata, GateType } from "../metadata";
+import { GateRenderData, GateType } from "../gateRenderData";
 import {
   minGateWidth,
   gateHeight,
@@ -27,57 +27,65 @@ import {
 } from "./formatUtils";
 
 /**
- * Given an array of operations (in metadata format), return the SVG representation.
+ * Given an array of operations render data, return the SVG representation.
  *
- * @param opsMetadata 2D array of Metadata representation of operations.
+ * @param renderData 2D array of rendering data for gates.
  * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
  *
  * @returns SVG representation of operations.
  */
 const formatGates = (
-  opsMetadata: Metadata[][],
+  renderData: GateRenderData[][],
   nestedDepth = 0,
 ): SVGElement => {
-  const formattedGates: SVGElement[] = opsMetadata
-    .map((col) => col.map((metadata) => formatGate(metadata, nestedDepth)))
+  const formattedGates: SVGElement[] = renderData
+    .map((col) => col.map((renderData) => formatGate(renderData, nestedDepth)))
     .flat();
   return group(formattedGates);
 };
 
 /**
- * Takes in an operation's metadata and formats it into SVG.
+ * Takes in an operation's rendering data and formats it into SVG.
  *
- * @param metadata Metadata object representation of gate.
+ * @param renderData The rendering data of the gate.
  * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
  *
  * @returns SVG representation of gate.
  */
-const formatGate = (metadata: Metadata, nestedDepth = 0): SVGElement => {
-  const { type, x, controlsY, targetsY, label, displayArgs, width } = metadata;
+const formatGate = (
+  renderData: GateRenderData,
+  nestedDepth = 0,
+): SVGElement => {
+  const { type, x, controlsY, targetsY, label, displayArgs, width } =
+    renderData;
   switch (type) {
     case GateType.Measure:
-      return _createGate([_measure(x, controlsY[0])], metadata, nestedDepth);
+      return _createGate([_measure(x, controlsY[0])], renderData, nestedDepth);
     case GateType.Unitary:
       return _createGate(
         [_unitary(label, x, targetsY as number[][], width, displayArgs)],
-        metadata,
+        renderData,
         nestedDepth,
       );
     case GateType.X:
-      return _createGate([_x(metadata, nestedDepth)], metadata, nestedDepth);
+      return _createGate([_x(renderData)], renderData, nestedDepth);
     case GateType.Ket:
-      return _createGate([_ket(label, metadata)], metadata, nestedDepth);
+      return _createGate([_ket(label, renderData)], renderData, nestedDepth);
     case GateType.Swap:
       return controlsY.length > 0
-        ? _controlledGate(metadata, nestedDepth)
-        : _createGate([_swap(metadata, nestedDepth)], metadata, nestedDepth);
+        ? _controlledGate(renderData, nestedDepth)
+        : _createGate(
+            [_swap(renderData, nestedDepth)],
+            renderData,
+            nestedDepth,
+          );
     case GateType.Cnot:
     case GateType.ControlledUnitary:
-      return _controlledGate(metadata, nestedDepth);
+      return _controlledGate(renderData, nestedDepth);
     case GateType.Group:
-      return _groupedOperations(metadata, nestedDepth);
+      return _groupedOperations(renderData, nestedDepth);
     case GateType.ClassicalControlled:
-      return _classicalControlled(metadata);
+      return _classicalControlled(renderData);
     default:
       throw new Error(`ERROR: unknown gate (${label}) of type ${type}.`);
   }
@@ -86,23 +94,24 @@ const formatGate = (metadata: Metadata, nestedDepth = 0): SVGElement => {
 /**
  * Groups SVG elements into a gate SVG group.
  *
- * @param svgElems       Array of SVG elements.
- * @param dataAttributes Custom data attributes to be attached to SVG group.
+ * @param svgElems - Array of SVG elements that make up the gate.
+ * @param renderData - Render data containing information about the gate, such as data attributes.
+ * @param nestedDepth - Depth of nested operation.
  *
  * @returns SVG representation of a gate.
  */
 const _createGate = (
   svgElems: SVGElement[],
-  metadata: Metadata,
+  renderData: GateRenderData,
   nestedDepth: number,
 ): SVGElement => {
-  const { dataAttributes } = metadata || {};
+  const { dataAttributes } = renderData || {};
   const attributes: { [attr: string]: string } = { class: "gate" };
   Object.entries(dataAttributes || {}).forEach(
     ([attr, val]) => (attributes[`data-${attr}`] = val),
   );
 
-  const zoomBtn: SVGElement | null = _zoomButton(metadata, nestedDepth);
+  const zoomBtn: SVGElement | null = _zoomButton(renderData, nestedDepth);
   if (zoomBtn != null) svgElems = svgElems.concat([zoomBtn]);
   return group(svgElems, attributes);
 };
@@ -111,19 +120,19 @@ const _createGate = (
  * Returns the expand/collapse button for an operation if it can be zoomed-in or zoomed-out,
  * respectively. If neither are allowed, return `null`.
  *
- * @param metadata Operation metadata.
+ * @param renderData Operation render data.
  * @param nestedDepth Depth of nested operation.
  *
  * @returns SVG element for expand/collapse button if needed, or null otherwise.
  */
 const _zoomButton = (
-  metadata: Metadata,
+  renderData: GateRenderData,
   nestedDepth: number,
 ): SVGElement | null => {
-  if (metadata == undefined) return null;
+  if (renderData == undefined) return null;
 
-  const [x1, y1] = _gatePosition(metadata, nestedDepth);
-  let { dataAttributes } = metadata;
+  const [x1, y1] = _gatePosition(renderData, nestedDepth);
+  let { dataAttributes } = renderData;
   dataAttributes = dataAttributes || {};
 
   const expanded = "expanded" in dataAttributes;
@@ -154,16 +163,16 @@ const _zoomButton = (
 /**
  * Calculate position of gate.
  *
- * @param metadata Operation metadata.
+ * @param renderData Operation render data.
  * @param nestedDepth Depth of nested operations.
  *
  * @returns Coordinates of gate: [x1, y1, x2, y2].
  */
 const _gatePosition = (
-  metadata: Metadata,
+  renderData: GateRenderData,
   nestedDepth: number,
 ): [number, number, number, number] => {
-  const { x, width, type, targetsY } = metadata;
+  const { x, width, type, targetsY } = renderData;
 
   const ys = targetsY?.flatMap((y) => y as number[]) || [];
   const maxY = Math.max(...ys);
@@ -315,18 +324,18 @@ const _unitaryBox = (
 };
 
 /**
- * Creates the SVG for a SWAP gate on y coords given by targetsY.
+ * Creates the SVG for a SWAP gate on y coords given by `renderData`.
  *
- * @param x          Centre x coord of SWAP gate.
- * @param targetsY   y coords of target registers.
+ * @param renderData - The render data containing information about the gate, including position and targets.
+ * @param nestedDepth - The depth of nested operations (used for adjusting padding and positioning).
  *
  * @returns SVG representation of SWAP gate.
  */
-const _swap = (metadata: Metadata, nestedDepth: number): SVGElement => {
-  const { x, targetsY } = metadata;
+const _swap = (renderData: GateRenderData, nestedDepth: number): SVGElement => {
+  const { x, targetsY } = renderData;
 
   // Get SVGs of crosses
-  const [x1, y1, x2, y2] = _gatePosition(metadata, nestedDepth);
+  const [x1, y1, x2, y2] = _gatePosition(renderData, nestedDepth);
   const ys = targetsY?.flatMap((y) => y as number[]) || [];
 
   const bg: SVGElement = box(x1, y1, x2, y2, "gate-swap");
@@ -339,11 +348,12 @@ const _swap = (metadata: Metadata, nestedDepth: number): SVGElement => {
 /**
  * Creates the SVG for an X gate
  *
+ * @param renderData - The render data containing information about the gate, including position and targets.
+ *
  * @returns SVG representation of X gate.
  */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const _x = (metadata: Metadata, _: number): SVGElement => {
-  const { x, targetsY } = metadata;
+const _x = (renderData: GateRenderData): SVGElement => {
+  const { x, targetsY } = renderData;
   const ys = targetsY.flatMap((y) => y as number[]);
   return _oplus(x, ys[0]);
 };
@@ -352,12 +362,12 @@ const _x = (metadata: Metadata, _: number): SVGElement => {
  * Creates the SVG for a ket notation (e.g "|0⟩" or "|1⟩") gate.
  *
  * @param label    The label for the ket notation (e.g., "0" or "1").
- * @param metadata The metadata object containing information about the gate's position and appearance.
+ * @param renderData The render data containing information about the gate's position and appearance.
  *
  * @returns SVG representation of the ket notation gate.
  */
-const _ket = (label: string, metadata: Metadata): SVGElement => {
-  const { x, targetsY, width } = metadata;
+const _ket = (label: string, renderData: GateRenderData): SVGElement => {
+  const { x, targetsY, width } = renderData;
   const gate = _unitary(
     `|${label}〉`,
     x,
@@ -399,17 +409,17 @@ const _cross = (x: number, y: number): SVGElement => {
 /**
  * Produces the SVG representation of a controlled gate on multiple qubits.
  *
- * @param metadata Metadata of controlled gate.
+ * @param renderData Render data of controlled gate.
  *
  * @returns SVG representation of controlled gate.
  */
 const _controlledGate = (
-  metadata: Metadata,
+  renderData: GateRenderData,
   nestedDepth: number,
 ): SVGElement => {
   const targetGateSvgs: SVGElement[] = [];
-  const { type, x, controlsY, label, displayArgs, width } = metadata;
-  let { targetsY } = metadata;
+  const { type, x, controlsY, label, displayArgs, width } = renderData;
+  let { targetsY } = renderData;
 
   // Get SVG for target gates
   switch (type) {
@@ -442,7 +452,7 @@ const _controlledGate = (
   vertLine.style.pointerEvents = "none";
   const svg: SVGElement = _createGate(
     [vertLine, ...controlledDotsSvg, ...targetGateSvgs],
-    metadata,
+    renderData,
     nestedDepth,
   );
   return svg;
@@ -467,42 +477,43 @@ const _oplus = (x: number, y: number, r = 15): SVGElement => {
 /**
  * Generates the SVG for a group of nested operations.
  *
- * @param metadata Metadata representation of gate.
+ * @param renderData Render data of gate.
  * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
  *
  * @returns SVG representation of gate.
  */
 const _groupedOperations = (
-  metadata: Metadata,
+  renderData: GateRenderData,
   nestedDepth: number,
 ): SVGElement => {
-  const { children } = metadata;
-  const [x1, y1, x2, y2] = _gatePosition(metadata, nestedDepth);
+  const { children } = renderData;
+  const [x1, y1, x2, y2] = _gatePosition(renderData, nestedDepth);
 
   // Draw dashed box around children gates
   const box: SVGElement = dashedBox(x1, y1, x2, y2);
   const elems: SVGElement[] = [box];
   if (children != null)
-    elems.push(formatGates(children as Metadata[][], nestedDepth + 1));
-  return _createGate(elems, metadata, nestedDepth);
+    elems.push(formatGates(children as GateRenderData[][], nestedDepth + 1));
+  return _createGate(elems, renderData, nestedDepth);
 };
 
 /**
  * Generates the SVG for a classically controlled group of operations.
  *
- * @param metadata Metadata representation of gate.
+ * @param renderData Render data of gate.
  * @param padding  Padding within dashed box.
  *
  * @returns SVG representation of gate.
  */
 const _classicalControlled = (
-  metadata: Metadata,
+  renderData: GateRenderData,
   padding: number = groupBoxPadding,
 ): SVGElement => {
-  const { controlsY, dataAttributes } = metadata;
-  const targetsY: number[] = metadata.targetsY as number[];
-  const children: Metadata[][][] = metadata.children as Metadata[][][];
-  let { x, width } = metadata;
+  const { controlsY, dataAttributes } = renderData;
+  const targetsY: number[] = renderData.targetsY as number[];
+  const children: GateRenderData[][][] =
+    renderData.children as GateRenderData[][][];
+  let { x, width } = renderData;
 
   const controlY = controlsY[0];
 
diff --git a/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts
index a932232b..36309513 100644
--- a/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts
+++ b/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts
@@ -2,7 +2,7 @@
 // Licensed under the MIT license.
 
 import { Qubit } from "../circuit";
-import { RegisterType, RegisterMap, RegisterMetadata } from "../register";
+import { RegisterType, RegisterMap, RegisterRenderData } from "../register";
 import {
   leftPadding,
   startY,
@@ -13,7 +13,7 @@ import { group, text } from "./formatUtils";
 
 /**
  * `formatInputs` takes in an array of Qubits and outputs the SVG string of formatted
- * qubit wires and a mapping from register IDs to register metadata (for rendering).
+ * qubit wires and a mapping from register IDs to register rendering data.
  *
  * @param qubits List of declared qubits.
  *
@@ -45,7 +45,7 @@ const formatInputs = (
 
     // Add classical wires
     registers[id].children = Array.from(Array(numResults), () => {
-      const clsReg: RegisterMetadata = {
+      const clsReg: RegisterRenderData = {
         type: RegisterType.Classical,
         y: currY,
       };
diff --git a/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts
index b9c3dca8..b6038a53 100644
--- a/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts
+++ b/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts
@@ -3,22 +3,22 @@
 
 import { RegisterMap } from "../register";
 import { regLineStart } from "../constants";
-import { Metadata, GateType } from "../metadata";
+import { GateRenderData, GateType } from "../gateRenderData";
 import { group, line } from "./formatUtils";
 
 /**
  * Generate the SVG representation of the qubit register wires in `registers` and the classical wires
  * stemming from each measurement gate.
  *
- * @param registers    Map from register IDs to register metadata.
- * @param measureGates Array of measurement gates metadata.
+ * @param registers    Map from register IDs to register render data.
+ * @param measureGates Array of measurement gate render data.
  * @param endX         End x coord.
  *
  * @returns SVG representation of register wires.
  */
 const formatRegisters = (
   registers: RegisterMap,
-  measureGates: Metadata[],
+  measureGates: GateRenderData[],
   endX: number,
 ): SVGElement => {
   const formattedRegs: SVGElement[] = [];
diff --git a/npm/qsharp/ux/circuit-vis/metadata.ts b/npm/qsharp/ux/circuit-vis/gateRenderData.ts
similarity index 89%
rename from npm/qsharp/ux/circuit-vis/metadata.ts
rename to npm/qsharp/ux/circuit-vis/gateRenderData.ts
index 4b921beb..77c83e53 100644
--- a/npm/qsharp/ux/circuit-vis/metadata.ts
+++ b/npm/qsharp/ux/circuit-vis/gateRenderData.ts
@@ -30,10 +30,10 @@ export enum GateType {
 }
 
 /**
- * Metadata used to store information pertaining to a given
+ * Rendering data used to store information pertaining to a given
  * operation for rendering its corresponding SVG.
  */
-export interface Metadata {
+export interface GateRenderData {
   /** Gate type. */
   type: GateType;
   /** Centre x coord for gate position. */
@@ -52,7 +52,7 @@ export interface Metadata {
   /** Gate width. */
   width: number;
   /** Children operations as part of group. */
-  children?: Metadata[][] | Metadata[][][];
+  children?: GateRenderData[][] | GateRenderData[][][];
   /** Custom data attributes to attach to gate element. */
   dataAttributes?: DataAttributes;
 }
diff --git a/npm/qsharp/ux/circuit-vis/panel.ts b/npm/qsharp/ux/circuit-vis/panel.ts
index 65961ddc..0a1ceb7b 100644
--- a/npm/qsharp/ux/circuit-vis/panel.ts
+++ b/npm/qsharp/ux/circuit-vis/panel.ts
@@ -9,7 +9,7 @@ import {
   verticalGap,
 } from "./constants";
 import { formatGate } from "./formatters/gateFormatter";
-import { GateType, Metadata } from "./metadata";
+import { GateType, GateRenderData } from "./gateRenderData";
 import { getGateWidth } from "./utils";
 
 /**
@@ -83,7 +83,7 @@ const _createToolbox = (): HTMLElement => {
   let prefixX = 0;
   let prefixY = 0;
   const gateElems = Object.keys(toolboxGateDictionary).map((key, index) => {
-    const { width: gateWidth } = toMetadata(toolboxGateDictionary[key], 0, 0);
+    const { width: gateWidth } = toRenderData(toolboxGateDictionary[key], 0, 0);
 
     // Increment prefixX for every gate, and reset after 2 gates (2 columns)
     if (index % 2 === 0 && index !== 0) {
@@ -169,19 +169,19 @@ const _title = (text: string): HTMLElement => {
 };
 
 /**
- * Wrapper to generate metadata based on _opToMetadata with mock registers and limited support
+ * Wrapper to generate render data based on _opToRenderData with mock registers and limited support
  * @param operation     Operation object
  * @param x             x coordinate at starting point from the left
  * @param y             y coordinate at starting point from the top
- * @returns             Metadata object
+ * @returns             GateRenderData object
  */
-const toMetadata = (
+const toRenderData = (
   operation: Operation | undefined,
   x: number,
   y: number,
-): Metadata => {
+): GateRenderData => {
   const target = y + 1 + gateHeight / 2; // offset by 1 for top padding
-  const metadata: Metadata = {
+  const renderData: GateRenderData = {
     type: GateType.Invalid,
     x: x + 1 + minGateWidth / 2, // offset by 1 for left padding
     controlsY: [],
@@ -190,54 +190,54 @@ const toMetadata = (
     width: -1,
   };
 
-  if (operation === undefined) return metadata;
+  if (operation === undefined) return renderData;
 
   switch (operation.kind) {
     case "unitary": {
       const { gate, controls } = operation;
 
       if (gate === "SWAP") {
-        metadata.type = GateType.Swap;
+        renderData.type = GateType.Swap;
       } else if (controls && controls.length > 0) {
-        metadata.type =
+        renderData.type =
           gate === "X" ? GateType.Cnot : GateType.ControlledUnitary;
-        metadata.label = gate;
+        renderData.label = gate;
         if (gate !== "X") {
-          metadata.targetsY = [[target]];
+          renderData.targetsY = [[target]];
         }
       } else if (gate === "X") {
-        metadata.type = GateType.X;
-        metadata.label = gate;
+        renderData.type = GateType.X;
+        renderData.label = gate;
       } else {
-        metadata.type = GateType.Unitary;
-        metadata.label = gate;
-        metadata.targetsY = [[target]];
+        renderData.type = GateType.Unitary;
+        renderData.label = gate;
+        renderData.targetsY = [[target]];
       }
       break;
     }
     case "measurement":
-      metadata.type = GateType.Measure;
-      metadata.controlsY = [target];
+      renderData.type = GateType.Measure;
+      renderData.controlsY = [target];
       break;
     case "ket":
-      metadata.type = GateType.Ket;
-      metadata.label = operation.gate;
-      metadata.targetsY = [[target]];
+      renderData.type = GateType.Ket;
+      renderData.label = operation.gate;
+      renderData.targetsY = [[target]];
       break;
   }
 
   if (operation.args !== undefined && operation.args.length > 0)
-    metadata.displayArgs = operation.args[0];
+    renderData.displayArgs = operation.args[0];
 
-  metadata.width = getGateWidth(metadata);
-  metadata.x = x + 1 + metadata.width / 2; // offset by 1 for left padding
+  renderData.width = getGateWidth(renderData);
+  renderData.x = x + 1 + renderData.width / 2; // offset by 1 for left padding
 
-  return metadata;
+  return renderData;
 };
 
 /**
  * Generate an SVG gate element for the Toolbox panel based on the type of gate.
- * This function retrieves the operation metadata from the gate dictionary,
+ * This function retrieves the operation render data from the gate dictionary,
  * formats the gate, and returns the corresponding SVG element.
  *
  * @param gateDictionary - The dictionary containing gate operations.
@@ -255,9 +255,9 @@ const _gate = (
 ): SVGElement => {
   const gate = gateDictionary[type];
   if (gate == null) throw new Error(`Gate ${type} not available`);
-  const metadata = toMetadata(gate, x, y);
-  metadata.dataAttributes = { type: type };
-  const gateElem = formatGate(metadata).cloneNode(true) as SVGElement;
+  const renderData = toRenderData(gate, x, y);
+  renderData.dataAttributes = { type: type };
+  const gateElem = formatGate(renderData).cloneNode(true) as SVGElement;
   gateElem.setAttribute("toolbox-item", "true");
 
   return gateElem;
@@ -322,7 +322,6 @@ const toolboxGateDictionary: GateDictionary = {
   H: _makeUnitary("H"),
   SX: _makeUnitary("SX"),
   Reset: _makeKet("0"),
-  ResetOne: _makeKet("1"),
   Measure: _makeMeasurement("Measure"),
 };
 
@@ -330,4 +329,4 @@ toolboxGateDictionary["RX"].params = [{ name: "theta", type: "Double" }];
 toolboxGateDictionary["RY"].params = [{ name: "theta", type: "Double" }];
 toolboxGateDictionary["RZ"].params = [{ name: "theta", type: "Double" }];
 
-export { createPanel, toolboxGateDictionary, toMetadata };
+export { createPanel, toolboxGateDictionary, toRenderData };
diff --git a/npm/qsharp/ux/circuit-vis/process.ts b/npm/qsharp/ux/circuit-vis/process.ts
index c09d39c2..6facda4c 100644
--- a/npm/qsharp/ux/circuit-vis/process.ts
+++ b/npm/qsharp/ux/circuit-vis/process.ts
@@ -9,26 +9,26 @@ import {
   groupBoxPadding,
 } from "./constants";
 import { ComponentGrid, Operation, ConditionalRender } from "./circuit";
-import { Metadata, GateType } from "./metadata";
+import { GateRenderData, GateType } from "./gateRenderData";
 import { Register, RegisterMap } from "./register";
 import { getGateWidth } from "./utils";
 
 /**
- * Takes in a component grid and maps the operations to `metadata` objects which
+ * Takes in a component grid and maps the operations to `GateRenderData` objects which
  * contains information for formatting the corresponding SVG.
  *
  * @param componentGrid Grid of circuit components.
- * @param registers  Mapping from qubit IDs to register metadata.
+ * @param registers  Mapping from qubit IDs to register render data.
  *
- * @returns An object containing `metadataArray` (2D Array of Metadata objects) and
+ * @returns An object containing `renderDataArray` (2D Array of GateRenderData objects) and
  *          `svgWidth` which is the width of the entire SVG.
  */
 const processOperations = (
   componentGrid: ComponentGrid,
   registers: RegisterMap,
-): { metadataArray: Metadata[][]; svgWidth: number } => {
+): { renderDataArray: GateRenderData[][]; svgWidth: number } => {
   if (componentGrid.length === 0)
-    return { metadataArray: [], svgWidth: startX };
+    return { renderDataArray: [], svgWidth: startX };
   const numColumns: number = componentGrid.length;
   const columnsWidths: number[] = new Array(numColumns).fill(minGateWidth);
 
@@ -36,68 +36,73 @@ const processOperations = (
   const classicalRegs: [number, Register][] =
     _getClassicalRegStart(componentGrid);
 
-  // Map operation index to gate metadata for formatting later
-  const opsMetadata: Metadata[][] = componentGrid.map((col, colIndex) =>
-    col.components.map((op) => {
-      const metadata: Metadata = _opToMetadata(op, registers);
-
-      if (
-        op != null &&
-        [GateType.Unitary, GateType.Ket, GateType.ControlledUnitary].includes(
-          metadata.type,
-        )
-      ) {
-        // If gate is a unitary type, split targetsY into groups if there
-        // is a classical register between them for rendering
-
-        // Get y coordinates of classical registers in the same column as this operation
-        const classicalRegY: number[] = classicalRegs
-          .filter(([regCol]) => regCol <= colIndex)
-          .map(([, reg]) => {
-            if (reg.result == null)
-              throw new Error("Could not find cId for classical register.");
-            const { children } = registers[reg.qubit];
-            if (children == null)
-              throw new Error(
-                `Failed to find classical registers for qubit ID ${reg.qubit}.`,
-              );
-            return children[reg.result].y;
-          });
-
-        let qubits: Register[];
-        switch (op.kind) {
-          case "unitary":
-            qubits = op.targets;
-            break;
-          case "measurement":
-            qubits = op.qubits;
-            break;
-          case "ket":
-            qubits = op.targets;
-            break;
-        }
+  // Map operation index to gate render data for formatting later
+  const renderDataArray: GateRenderData[][] = componentGrid.map(
+    (col, colIndex) =>
+      col.components.map((op) => {
+        const renderData: GateRenderData = _opToRenderData(op, registers);
+
+        if (
+          op != null &&
+          [GateType.Unitary, GateType.Ket, GateType.ControlledUnitary].includes(
+            renderData.type,
+          )
+        ) {
+          // If gate is a unitary type, split targetsY into groups if there
+          // is a classical register between them for rendering
+
+          // Get y coordinates of classical registers in the same column as this operation
+          const classicalRegY: number[] = classicalRegs
+            .filter(([regCol]) => regCol <= colIndex)
+            .map(([, reg]) => {
+              if (reg.result == null)
+                throw new Error("Could not find cId for classical register.");
+              const { children } = registers[reg.qubit];
+              if (children == null)
+                throw new Error(
+                  `Failed to find classical registers for qubit ID ${reg.qubit}.`,
+                );
+              return children[reg.result].y;
+            });
+
+          let qubits: Register[];
+          switch (op.kind) {
+            case "unitary":
+              qubits = op.targets;
+              break;
+            case "measurement":
+              qubits = op.qubits;
+              break;
+            case "ket":
+              qubits = op.targets;
+              break;
+          }
 
-        metadata.targetsY = _splitTargetsY(qubits, classicalRegY, registers);
-      }
+          renderData.targetsY = _splitTargetsY(
+            qubits,
+            classicalRegY,
+            registers,
+          );
+        }
 
-      // Expand column size, if needed
-      if (metadata.width > columnsWidths[colIndex]) {
-        columnsWidths[colIndex] = metadata.width;
-      }
+        // Expand column size, if needed
+        if (renderData.width > columnsWidths[colIndex]) {
+          columnsWidths[colIndex] = renderData.width;
+        }
 
-      return metadata;
-    }),
+        return renderData;
+      }),
   );
 
   // Filter out invalid gates
-  const metadataArray: Metadata[][] = opsMetadata
+  const filteredArray: GateRenderData[][] = renderDataArray
     .map((col) => col.filter(({ type }) => type != GateType.Invalid))
     .filter((col) => col.length > 0);
 
   // Fill in x coord of each gate
-  const endX: number = _fillMetadataX(metadataArray, columnsWidths);
+  const endX: number = _fillRenderDataX(filteredArray, columnsWidths);
 
-  return { metadataArray, svgWidth: endX };
+  return { renderDataArray: filteredArray, svgWidth: endX };
 };
 
 /**
@@ -125,19 +130,19 @@ const _getClassicalRegStart = (
 };
 
 /**
- * Maps operation to metadata (e.g. gate type, position, dimensions, text)
+ * Maps operation to render data (e.g. gate type, position, dimensions, text)
  * required to render the image.
  *
- * @param op        Operation to be mapped into metadata format.
+ * @param op        Operation to be mapped into render data.
  * @param registers Array of registers.
  *
- * @returns Metadata representation of given operation.
+ * @returns GateRenderData representation of given operation.
  */
-const _opToMetadata = (
+const _opToRenderData = (
   op: Operation | null,
   registers: RegisterMap,
-): Metadata => {
-  const metadata: Metadata = {
+): GateRenderData => {
+  const renderData: GateRenderData = {
     type: GateType.Invalid,
     x: 0,
     controlsY: [],
@@ -146,7 +151,7 @@ const _opToMetadata = (
     width: -1,
   };
 
-  if (op == null) return metadata;
+  if (op == null) return renderData;
 
   let isAdjoint: boolean;
   let controls: Register[] | undefined;
@@ -179,8 +184,8 @@ const _opToMetadata = (
   } = op;
 
   // Set y coords
-  metadata.controlsY = controls?.map((reg) => _getRegY(reg, registers)) || [];
-  metadata.targetsY = targets.map((reg) => _getRegY(reg, registers));
+  renderData.controlsY = controls?.map((reg) => _getRegY(reg, registers)) || [];
+  renderData.targetsY = targets.map((reg) => _getRegY(reg, registers));
 
   if (isConditional) {
     // Classically-controlled operations
@@ -198,7 +203,7 @@ const _opToMetadata = (
       }))
       .filter((col) => col.components.length > 0);
     let childrenInstrs = processOperations(onZeroOps, registers);
-    const zeroGates: Metadata[][] = childrenInstrs.metadataArray;
+    const zeroGates: GateRenderData[][] = childrenInstrs.renderDataArray;
     const zeroChildWidth: number = childrenInstrs.svgWidth;
 
     // Gates to display when classical bit is 1.
@@ -210,77 +215,80 @@ const _opToMetadata = (
       }))
       .filter((col) => col.components.length > 0);
     childrenInstrs = processOperations(onOneOps, registers);
-    const oneGates: Metadata[][] = childrenInstrs.metadataArray;
+    const oneGates: GateRenderData[][] = childrenInstrs.renderDataArray;
     const oneChildWidth: number = childrenInstrs.svgWidth;
 
     // Subtract startX (left-side) and 2*gatePadding (right-side) from nested child gates width
     const width: number =
       Math.max(zeroChildWidth, oneChildWidth) - startX - gatePadding * 2;
 
-    metadata.type = GateType.ClassicalControlled;
-    metadata.children = [zeroGates, oneGates];
+    renderData.type = GateType.ClassicalControlled;
+    renderData.children = [zeroGates, oneGates];
     // Add additional width from control button and inner box padding for dashed box
-    metadata.width = width + controlBtnOffset + groupBoxPadding * 2;
+    renderData.width = width + controlBtnOffset + groupBoxPadding * 2;
 
     // Set targets to first and last quantum registers so we can render the surrounding box
     // around all quantum registers.
     const qubitsY: number[] = Object.values(registers).map(({ y }) => y);
     if (qubitsY.length > 0)
-      metadata.targetsY = [Math.min(...qubitsY), Math.max(...qubitsY)];
+      renderData.targetsY = [Math.min(...qubitsY), Math.max(...qubitsY)];
   } else if (
     conditionalRender == ConditionalRender.AsGroup &&
     (children?.length || 0) > 0
   ) {
     const childrenInstrs = processOperations(children!, registers);
-    metadata.type = GateType.Group;
-    metadata.children = childrenInstrs.metadataArray;
+    renderData.type = GateType.Group;
+    renderData.children = childrenInstrs.renderDataArray;
     // _zoomButton function in gateFormatter.ts relies on
     // 'expanded' attribute to render zoom button
-    metadata.dataAttributes = { expanded: "true" };
+    renderData.dataAttributes = { expanded: "true" };
     // Subtract startX (left-side) and add inner box padding (minus nested gate padding) for dashed box
-    metadata.width =
+    renderData.width =
       childrenInstrs.svgWidth - startX + (groupBoxPadding - gatePadding) * 2;
   } else if (op.kind === "measurement") {
-    metadata.type = GateType.Measure;
+    renderData.type = GateType.Measure;
   } else if (op.kind === "ket") {
-    metadata.type = GateType.Ket;
-    metadata.label = gate;
+    renderData.type = GateType.Ket;
+    renderData.label = gate;
   } else if (gate === "SWAP") {
-    metadata.type = GateType.Swap;
+    renderData.type = GateType.Swap;
   } else if (controls && controls.length > 0) {
-    metadata.type = gate === "X" ? GateType.Cnot : GateType.ControlledUnitary;
-    metadata.label = gate;
+    renderData.type = gate === "X" ? GateType.Cnot : GateType.ControlledUnitary;
+    renderData.label = gate;
   } else if (gate === "X") {
-    metadata.type = GateType.X;
-    metadata.label = gate;
+    renderData.type = GateType.X;
+    renderData.label = gate;
   } else {
     // Any other gate treated as a simple unitary gate
-    metadata.type = GateType.Unitary;
-    metadata.label = gate;
+    renderData.type = GateType.Unitary;
+    renderData.label = gate;
   }
 
   // If adjoint, add ' to the end of gate label
-  if (isAdjoint && metadata.label.length > 0) metadata.label += "'";
+  if (isAdjoint && renderData.label.length > 0) renderData.label += "'";
 
   // If gate has extra arguments, display them
   // For now, we only display the first argument
-  if (args !== undefined && args.length > 0) metadata.displayArgs = args[0];
+  if (args !== undefined && args.length > 0) renderData.displayArgs = args[0];
 
   // Set gate width
-  metadata.width = getGateWidth(metadata);
+  renderData.width = getGateWidth(renderData);
 
   // Extend existing data attributes with user-provided data attributes
   if (dataAttributes != null)
-    metadata.dataAttributes = { ...metadata.dataAttributes, ...dataAttributes };
+    renderData.dataAttributes = {
+      ...renderData.dataAttributes,
+      ...dataAttributes,
+    };
 
-  return metadata;
+  return renderData;
 };
 
 /**
  * Compute the y coord of a given register.
  *
  * @param reg       Register to compute y coord of.
- * @param registers Map of qubit IDs to RegisterMetadata.
+ * @param registers Map of qubit IDs to RegisterRenderData.
  *
  * @returns The y coord of give register.
  */
@@ -309,7 +317,7 @@ const _getRegY = (reg: Register, registers: RegisterMap): number => {
  *
  * @param targets       Target qubit registers.
  * @param classicalRegY y coords of classical registers overlapping current column.
- * @param registers     Mapping from register qubit IDs to register metadata.
+ * @param registers     Mapping from register qubit IDs to register render data.
  *
  * @returns Groups of target qubit y coords.
  */
@@ -368,15 +376,15 @@ const _splitTargetsY = (
 };
 
 /**
- * Updates the x coord of each metadata in the given 2D array of metadata and returns rightmost x coord.
+ * Updates the x coord of each render data object in the given 2D array and returns rightmost x coord.
  *
- * @param opsMetadata  2D array of metadata.
+ * @param renderDataArray  2D array of render data.
  * @param columnWidths Array of column widths.
  *
  * @returns Rightmost x coord.
  */
-const _fillMetadataX = (
-  opsMetadata: Metadata[][],
+const _fillRenderDataX = (
+  renderDataArray: GateRenderData[][],
   columnWidths: number[],
 ): number => {
   let endX: number = startX;
@@ -387,29 +395,29 @@ const _fillMetadataX = (
     return x;
   });
 
-  opsMetadata.forEach((col, colIndex) =>
-    col.forEach((metadata) => {
+  renderDataArray.forEach((col, colIndex) =>
+    col.forEach((renderData) => {
       const x = colStartX[colIndex];
-      switch (metadata.type) {
+      switch (renderData.type) {
         case GateType.ClassicalControlled:
         case GateType.Group:
           {
             // Subtract startX offset from nested gates and add offset and padding
             let offset: number = x - startX + groupBoxPadding;
-            if (metadata.type === GateType.ClassicalControlled)
+            if (renderData.type === GateType.ClassicalControlled)
               offset += controlBtnOffset;
 
             // Offset each x coord in children gates
-            _offsetChildrenX(metadata.children, offset);
+            _offsetChildrenX(renderData.children, offset);
 
             // We don't use the centre x coord because we only care about the rightmost x for
             // rendering the box around the group of nested gates
-            metadata.x = x;
+            renderData.x = x;
           }
           break;
 
         default:
-          metadata.x = x + columnWidths[colIndex] / 2;
+          renderData.x = x + columnWidths[colIndex] / 2;
           break;
       }
     }),
@@ -421,11 +429,11 @@ const _fillMetadataX = (
 /**
  * Offset x coords of nested children operations.
  *
- * @param children 2D or 3D array of children metadata.
+ * @param children 2D or 3D array of children GateRenderData.
  * @param offset   x coord offset.
  */
 const _offsetChildrenX = (
-  children: Metadata[][] | Metadata[][][] | undefined,
+  children: GateRenderData[][] | GateRenderData[][][] | undefined,
   offset: number,
 ): void => {
   if (children == null) return;
diff --git a/npm/qsharp/ux/circuit-vis/register.ts b/npm/qsharp/ux/circuit-vis/register.ts
index ffcd9cd2..8f1360b5 100644
--- a/npm/qsharp/ux/circuit-vis/register.ts
+++ b/npm/qsharp/ux/circuit-vis/register.ts
@@ -5,5 +5,5 @@ export {
   RegisterType,
   type Register,
   type RegisterMap,
-  type RegisterMetadata,
+  type RegisterRenderData,
 } from "../../src/shared/register";
diff --git a/npm/qsharp/ux/circuit-vis/sqore.ts b/npm/qsharp/ux/circuit-vis/sqore.ts
index 69cd9af5..0048b418 100644
--- a/npm/qsharp/ux/circuit-vis/sqore.ts
+++ b/npm/qsharp/ux/circuit-vis/sqore.ts
@@ -13,7 +13,7 @@ import {
   Operation,
   Column,
 } from "./circuit";
-import { Metadata, GateType } from "./metadata";
+import { GateRenderData, GateType } from "./gateRenderData";
 import { createUUID } from "./utils";
 import { gateHeight, minGateWidth, minToolboxHeight, svgNS } from "./constants";
 import { createDragzones } from "./draggable";
@@ -21,7 +21,7 @@ import { enableEvents } from "./events";
 import { createPanel } from "./panel";
 
 /**
- * Contains metadata for visualization.
+ * Contains render data for visualization.
  */
 interface ComposedSqore {
   /** Width of visualization. */
@@ -171,10 +171,13 @@ export class Sqore {
    *
    * @param circuit Circuit to be visualized.
    *
-   * @returns `ComposedSqore` object containing metadata for visualization.
+   * @returns `ComposedSqore` object containing render data for visualization.
    */
   private compose(circuit: Circuit): ComposedSqore {
-    const add = (acc: Metadata[], gate: Metadata | Metadata[]): void => {
+    const add = (
+      acc: GateRenderData[],
+      gate: GateRenderData | GateRenderData[],
+    ): void => {
       if (Array.isArray(gate)) {
         gate.forEach((g) => add(acc, g));
       } else {
@@ -183,20 +186,20 @@ export class Sqore {
       }
     };
 
-    const flatten = (metadata: Metadata[][]): Metadata[] => {
-      const result: Metadata[] = [];
-      metadata.forEach((col) => col.forEach((g) => add(result, g)));
+    const flatten = (renderData: GateRenderData[][]): GateRenderData[] => {
+      const result: GateRenderData[] = [];
+      renderData.forEach((col) => col.forEach((g) => add(result, g)));
       return result;
     };
 
     const { qubits, componentGrid } = circuit;
     const { qubitWires, registers, svgHeight } = formatInputs(qubits);
-    const { metadataArray, svgWidth } = processOperations(
+    const { renderDataArray, svgWidth } = processOperations(
       componentGrid,
       registers,
     );
-    const formattedGates: SVGElement = formatGates(metadataArray);
-    const measureGates: Metadata[] = flatten(metadataArray).filter(
+    const formattedGates: SVGElement = formatGates(renderDataArray);
+    const measureGates: GateRenderData[] = flatten(renderDataArray).filter(
       ({ type }) => type === GateType.Measure,
     );
     const formattedRegs: SVGElement = formatRegisters(
@@ -468,6 +471,7 @@ export class Sqore {
 
   // Minimize the circuits in a circuit group to remove dataAttributes
   minimizeCircuits(circuitGroup: CircuitGroup): CircuitGroup {
+    // Create a deep copy of the circuit group
     const minimizedCircuits: CircuitGroup = JSON.parse(
       JSON.stringify(circuitGroup),
     );
diff --git a/npm/qsharp/ux/circuit-vis/utils.ts b/npm/qsharp/ux/circuit-vis/utils.ts
index 04729745..a8bad8ca 100644
--- a/npm/qsharp/ux/circuit-vis/utils.ts
+++ b/npm/qsharp/ux/circuit-vis/utils.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
 
-import { Metadata, GateType } from "./metadata";
+import { GateRenderData, GateType } from "./gateRenderData";
 import {
   minGateWidth,
   labelPadding,
@@ -11,6 +11,44 @@ import {
 import { ComponentGrid, Operation } from "./circuit";
 import { Register } from "./register";
 
+/**
+ * Performs a deep equality check between two objects or arrays.
+ * @param obj1 - The first object or array to compare.
+ * @param obj2 - The second object or array to compare.
+ * @returns True if the objects are deeply equal, false otherwise.
+ */
+export const deepEqual = (obj1: unknown, obj2: unknown): boolean => {
+  if (obj1 === obj2) return true;
+
+  if (
+    obj1 === null ||
+    obj2 === null ||
+    typeof obj1 !== "object" ||
+    typeof obj2 !== "object"
+  ) {
+    return false;
+  }
+
+  const keys1 = Object.keys(obj1);
+  const keys2 = Object.keys(obj2);
+
+  if (keys1.length !== keys2.length) return false;
+
+  for (const key of keys1) {
+    if (
+      !keys2.includes(key) ||
+      !deepEqual(
+        (obj1 as Record<string, unknown>)[key],
+        (obj2 as Record<string, unknown>)[key],
+      )
+    ) {
+      return false;
+    }
+  }
+
+  return true;
+};
+
 /**
  * Generate a UUID using `Math.random`.
  * Note: this implementation came from https://stackoverflow.com/questions/105034/how-to-create-guid-uuid
@@ -26,9 +64,9 @@ const createUUID = (): string =>
   });
 
 /**
- * Calculate the width of a gate, given its metadata.
+ * Calculate the width of a gate, given its render data.
  *
- * @param metadata Metadata of a given gate.
+ * @param renderData - The rendering data of the gate, including its type, label, display arguments, and width.
  *
  * @returns Width of given gate (in pixels).
  */
@@ -37,7 +75,7 @@ const getGateWidth = ({
   label,
   displayArgs,
   width,
-}: Metadata): number => {
+}: GateRenderData): number => {
   if (width > 0) return width;
 
   switch (type) {
diff --git a/npm/qsharp/ux/qsharp-circuit.css b/npm/qsharp/ux/qsharp-circuit.css
index fc1e09a8..0b14ea27 100644
--- a/npm/qsharp/ux/qsharp-circuit.css
+++ b/npm/qsharp/ux/qsharp-circuit.css
@@ -2,287 +2,376 @@
    Licensed under the MIT License. */
 
 /* Circuit panel */
-.qs-circuit-panel p {
-  padding: 10px 0px;
-}
+.qs-circuit-panel {
+  p {
+    padding: 10px 0px;
+  }
 
-/* Error div in circuit panel */
-.qs-circuit-error {
-  padding: 10px 0px;
-}
+  /* Error div in circuit panel */
+  .qs-circuit-error {
+    padding: 10px 0px;
+  }
 
-.qs-circuit-error p {
-  padding: 10px 0px;
-}
+  .qs-circuit-error p {
+    padding: 10px 0px;
+  }
 
-/*
+  /*
   Styles for Q# circuits.
   Copied and modified from the default CSS generated by
   https://github.com/microsoft/quantum-viz.js/blob/288e0cb506ee866ecca1b414c522e2d634f7b36d/src/styles.ts
 */
 
-/* Background color for the circuit diagram */
-.qs-circuit {
-  background: var(--main-background);
-}
-
-/* Wires and gate outlines */
-.qs-circuit line,
-.qs-circuit circle,
-.qs-circuit rect {
-  stroke: var(--main-color);
-  stroke-width: 1;
-}
-
-/* Text for qubit labels */
-.qs-circuit text {
-  fill: var(--main-color);
-  dominant-baseline: middle;
-  text-anchor: middle;
-  font-family: Arial;
-}
-
-/* Background for gates */
-.qs-circuit .gate-unitary {
-  fill: var(--vscode-button-secondaryBackground, #333333);
-}
-
-/* Text for gates */
-.qs-circuit .gate text {
-  fill: var(--vscode-button-secondaryForeground, #ffffff);
-}
-
-/* Default style for arg-button */
-.qs-circuit .arg-button {
-  fill: var(--vscode-editor-foreground, #ffffff);
-  text-decoration: none;
-  cursor: default;
-  pointer-events: none;
-}
-
-/* Edit mode style for arg-button (hyperlink appearance) */
-.qs-circuit .arg-button.edit-mode {
-  fill: var(--vscode-textLink-foreground, #0066cc);
-  text-decoration: underline;
-  cursor: pointer;
-  pointer-events: all;
-}
-
-/* Hover style for arg-button in edit mode */
-.qs-circuit .arg-button.edit-mode:hover {
-  fill: var(--vscode-textLink-activeForeground, #004499); /* Hover color */
-}
-
-/* Dot and line for controlled gates */
-.qs-circuit .gate > line,
-.qs-circuit .control-dot {
-  fill: var(--main-color);
-}
-
-/* X gate */
-.qs-circuit .gate > .oplus > line,
-.qs-circuit .gate > .oplus > circle {
-  fill: var(--main-background);
-  stroke: var(--main-color);
-  stroke-width: 2;
-}
-
-/* Measurement gate */
-.qs-circuit .gate-measure {
-  fill: var(--vscode-button-background, #007acc);
-}
-
-/* Measurement gate icon */
-.qs-circuit .gate > g > line,
-.qs-circuit .arc-measure {
-  stroke: var(--vscode-button-foreground, #ffffff);
-  fill: none;
-  stroke-width: 1;
-}
-
-/* Ket gate */
-.qs-circuit .gate-ket {
-  fill: var(--vscode-button-background, #007acc);
-}
-
-/* Ket gate text */
-.qs-circuit text.ket-text {
-  fill: var(--vscode-button-foreground, #ffffff);
-  stroke: none;
-}
-
-/* SWAP gate */
-.qs-circuit rect.gate-swap {
-  fill: transparent;
-  stroke: transparent;
-}
-
-/* Classical wire */
-.qs-circuit .register-classical {
-  stroke-width: 0.5;
-}
-
-/*
+  /* Background color for the circuit diagram */
+  .qs-circuit {
+    background: var(--main-background);
+  }
+
+  /* Wires and gate outlines */
+  .qs-circuit line,
+  .qs-circuit circle,
+  .qs-circuit rect {
+    stroke: var(--main-color);
+    stroke-width: 1;
+  }
+
+  /* Text for qubit labels */
+  .qs-circuit text {
+    fill: var(--main-color);
+    dominant-baseline: middle;
+    text-anchor: middle;
+    font-family: Arial;
+  }
+
+  /* Background for gates */
+  .qs-circuit .gate-unitary {
+    fill: var(--vscode-button-secondaryBackground, #333333);
+  }
+
+  /* Text for gates */
+  .qs-circuit .gate text {
+    fill: var(--vscode-button-secondaryForeground, #ffffff);
+  }
+
+  /* Default style for arg-button */
+  .qs-circuit .arg-button {
+    fill: var(--vscode-editor-foreground, #ffffff);
+    text-decoration: none;
+    cursor: default;
+    pointer-events: none;
+  }
+
+  /* Edit mode style for arg-button (hyperlink appearance) */
+  .qs-circuit .arg-button.edit-mode {
+    fill: var(--vscode-textLink-foreground, #0066cc);
+    text-decoration: underline;
+    cursor: pointer;
+    pointer-events: all;
+  }
+
+  /* Hover style for arg-button in edit mode */
+  .qs-circuit .arg-button.edit-mode:hover {
+    fill: var(--vscode-textLink-activeForeground, #004499); /* Hover color */
+  }
+
+  /* Dot and line for controlled gates */
+  .qs-circuit .gate > line,
+  .qs-circuit .control-dot {
+    fill: var(--main-color);
+  }
+
+  /* X gate */
+  .qs-circuit .gate > .oplus > line,
+  .qs-circuit .gate > .oplus > circle {
+    fill: var(--main-background);
+    stroke: var(--main-color);
+    stroke-width: 2;
+  }
+
+  /* Measurement gate */
+  .qs-circuit .gate-measure {
+    fill: var(--vscode-button-background, #007acc);
+  }
+
+  /* Measurement gate icon */
+  .qs-circuit .gate > g > line,
+  .qs-circuit .arc-measure {
+    stroke: var(--vscode-button-foreground, #ffffff);
+    fill: none;
+    stroke-width: 1;
+  }
+
+  /* Ket gate */
+  .qs-circuit .gate-ket {
+    fill: var(--vscode-button-background, #007acc);
+  }
+
+  /* Ket gate text */
+  .qs-circuit text.ket-text {
+    fill: var(--vscode-button-foreground, #ffffff);
+    stroke: none;
+  }
+
+  /* SWAP gate */
+  .qs-circuit rect.gate-swap {
+    fill: transparent;
+    stroke: transparent;
+  }
+
+  /* Classical wire */
+  .qs-circuit .register-classical {
+    stroke-width: 0.5;
+  }
+
+  /*
   The following styles are unused so they are unthemed.
   They relate to conditional circuits which are not currently supported.
 */
 
-.qs-circuit .hidden {
-  display: none;
-}
-
-.qs-circuit .classically-controlled-unknown {
-  opacity: 0.25;
-}
-
-.qs-circuit .classically-controlled-one .classical-container,
-.qs-circuit .classically-controlled-one .classical-line {
-  stroke: #4059bd;
-  stroke-width: 1.3;
-  fill: #4059bd;
-  fill-opacity: 0.1;
-}
-
-.qs-circuit .classically-controlled-zero .classical-container,
-.qs-circuit .classically-controlled-zero .classical-line {
-  stroke: #c40000;
-  stroke-width: 1.3;
-  fill: #c40000;
-  fill-opacity: 0.1;
-}
-
-.qs-circuit .classically-controlled-btn {
-  cursor: pointer;
-}
-
-.qs-circuit .classically-controlled-unknown .classically-controlled-btn {
-  fill: #e5e5e5;
-}
-
-.qs-circuit .classically-controlled-one .classically-controlled-btn {
-  fill: #4059bd;
-}
-
-.qs-circuit .classically-controlled-zero .classically-controlled-btn {
-  fill: #c40000;
-}
-
-.qs-circuit .classically-controlled-btn text {
-  dominant-baseline: middle;
-  text-anchor: middle;
-  stroke: none;
-  font-family: Arial;
-}
-
-.qs-circuit .classically-controlled-unknown .classically-controlled-btn text {
-  fill: #000000;
-}
-
-.qs-circuit .classically-controlled-one .classically-controlled-btn text {
-  fill: #ffffff;
-}
-
-.qs-circuit .classically-controlled-zero .classically-controlled-btn text {
-  fill: #ffffff;
-}
-
-.qs-circuit .qviz .gate-collapse,
-.qs-circuit .qviz .gate-expand {
-  opacity: 0;
-  transition: opacity 1s;
-}
-
-.qs-circuit .qviz:hover .gate-collapse,
-.qs-circuit .qviz:hover .gate-expand {
-  visibility: visible;
-  opacity: 0.2;
-  transition: visibility 1s;
-  transition: opacity 1s;
-}
-
-.qs-circuit .gate-expand,
-.gate-collapse {
-  cursor: pointer;
-}
-
-.qs-circuit .gate-collapse circle,
-.qs-circuit .gate-expand circle {
-  fill: white;
-  stroke-width: 2px;
-  stroke: black;
-}
-
-.qs-circuit .gate-collapse path,
-.qs-circuit .gate-expand path {
-  stroke-width: 4px;
-  stroke: black;
-}
-
-.qs-circuit .gate:hover > .gate-collapse,
-.qs-circuit .gate:hover > .gate-expand {
-  visibility: visible;
-  opacity: 1;
-  transition: opacity 1s;
-}
-
-text {
-  user-select: none;
-  pointer-events: none;
-}
-.dropzone-layer {
-  display: none;
-}
-.dropzone {
-  fill-opacity: 0%;
-  stroke-opacity: 0%;
-}
-.dropzone:hover {
-  fill: var(--vscode-editor-selectionBackground, #ec7063);
-  fill-opacity: 50%;
-}
-.dropzone-full-wire {
-  fill-opacity: 0%;
-  stroke-opacity: 0%;
-}
-.dropzone-full-wire:hover {
-  fill: var(--vscode-editor-selectionBackground, #ec7063);
-  fill-opacity: 50%;
-}
-.grab {
-  cursor: grab;
-}
-.moving {
-  cursor: move;
-}
-.copying {
-  cursor: copy;
-}
-
-.panel {
-  display: flex;
-  flex-direction: row;
-}
-
-.context-menu {
-  position: absolute;
-  background-color: var(--vscode-menu-background, #ffffff);
-  border: 1px solid var(--vscode-menu-border, #cccccc);
-  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
-  z-index: 1000;
-  padding: 5px 0;
-  border-radius: 4px;
-}
-
-.context-menu-option {
-  padding: 8px 12px;
-  cursor: pointer;
-  white-space: nowrap;
-  color: var(--vscode-menu-foreground, #000000);
-}
-
-.context-menu-option:hover {
-  background-color: var(--vscode-menu-selectionBackground, #f0f0f0);
-  color: var(--vscode-menu-selectionForeground, #000000);
+  .qs-circuit .hidden {
+    display: none;
+  }
+
+  .qs-circuit .classically-controlled-unknown {
+    opacity: 0.25;
+  }
+
+  .qs-circuit .classically-controlled-one .classical-container,
+  .qs-circuit .classically-controlled-one .classical-line {
+    stroke: #4059bd;
+    stroke-width: 1.3;
+    fill: #4059bd;
+    fill-opacity: 0.1;
+  }
+
+  .qs-circuit .classically-controlled-zero .classical-container,
+  .qs-circuit .classically-controlled-zero .classical-line {
+    stroke: #c40000;
+    stroke-width: 1.3;
+    fill: #c40000;
+    fill-opacity: 0.1;
+  }
+
+  .qs-circuit .classically-controlled-btn {
+    cursor: pointer;
+  }
+
+  .qs-circuit .classically-controlled-unknown .classically-controlled-btn {
+    fill: #e5e5e5;
+  }
+
+  .qs-circuit .classically-controlled-one .classically-controlled-btn {
+    fill: #4059bd;
+  }
+
+  .qs-circuit .classically-controlled-zero .classically-controlled-btn {
+    fill: #c40000;
+  }
+
+  .qs-circuit .classically-controlled-btn text {
+    dominant-baseline: middle;
+    text-anchor: middle;
+    stroke: none;
+    font-family: Arial;
+  }
+
+  .qs-circuit .classically-controlled-unknown .classically-controlled-btn text {
+    fill: #000000;
+  }
+
+  .qs-circuit .classically-controlled-one .classically-controlled-btn text {
+    fill: #ffffff;
+  }
+
+  .qs-circuit .classically-controlled-zero .classically-controlled-btn text {
+    fill: #ffffff;
+  }
+
+  .qs-circuit .qviz .gate-collapse,
+  .qs-circuit .qviz .gate-expand {
+    opacity: 0;
+    transition: opacity 1s;
+  }
+
+  .qs-circuit .qviz:hover .gate-collapse,
+  .qs-circuit .qviz:hover .gate-expand {
+    visibility: visible;
+    opacity: 0.2;
+    transition: visibility 1s;
+    transition: opacity 1s;
+  }
+
+  .qs-circuit .gate-expand,
+  .gate-collapse {
+    cursor: pointer;
+  }
+
+  .qs-circuit .gate-collapse circle,
+  .qs-circuit .gate-expand circle {
+    fill: white;
+    stroke-width: 2px;
+    stroke: black;
+  }
+
+  .qs-circuit .gate-collapse path,
+  .qs-circuit .gate-expand path {
+    stroke-width: 4px;
+    stroke: black;
+  }
+
+  .qs-circuit .gate:hover > .gate-collapse,
+  .qs-circuit .gate:hover > .gate-expand {
+    visibility: visible;
+    opacity: 1;
+    transition: opacity 1s;
+  }
+
+  text {
+    user-select: none;
+    pointer-events: none;
+  }
+  .dropzone-layer {
+    display: none;
+  }
+  .dropzone {
+    fill-opacity: 0%;
+    stroke-opacity: 0%;
+  }
+  .dropzone:hover {
+    fill: var(--vscode-editor-selectionBackground, #ec7063);
+    fill-opacity: 50%;
+  }
+  .dropzone-full-wire {
+    fill-opacity: 0%;
+    stroke-opacity: 0%;
+  }
+  .dropzone-full-wire:hover {
+    fill: var(--vscode-editor-selectionBackground, #ec7063);
+    fill-opacity: 50%;
+  }
+  .grab {
+    cursor: grab;
+  }
+  .moving {
+    cursor: move;
+  }
+  .copying {
+    cursor: copy;
+  }
+
+  .panel {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .context-menu {
+    position: absolute;
+    background-color: var(--vscode-menu-background, #ffffff);
+    border: 1px solid var(--vscode-menu-border, #cccccc);
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+    z-index: 1000;
+    padding: 5px 0;
+    border-radius: 4px;
+  }
+
+  .context-menu-option {
+    padding: 8px 12px;
+    cursor: pointer;
+    white-space: nowrap;
+    color: var(--vscode-menu-foreground, #000000);
+  }
+
+  .context-menu-option:hover {
+    background-color: var(--vscode-menu-selectionBackground, #f0f0f0);
+    color: var(--vscode-menu-selectionForeground, #000000);
+  }
+
+  .container {
+    display: flex;
+  }
+
+  .edit-panel {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .title {
+    margin: 0 0 4px;
+    font-size: 1.2rem;
+    user-select: none;
+  }
+
+  .block {
+    display: block;
+  }
+
+  .target-input,
+  .controls-input,
+  .parameters-input {
+    margin-bottom: 8px;
+  }
+
+  .toolbox-panel {
+    width: 100px;
+    min-height: var(--minToolboxHeight);
+    border-right: medium solid var(--vscode-editorWidget-border, #000000);
+  }
+
+  .toolbox-panel .title {
+    margin-top: 6px;
+  }
+
+  .toolbox-panel-svg {
+    width: -webkit-fill-available;
+    min-height: var(--minToolboxHeight);
+    height: 100%;
+  }
+
+  .ghost {
+    width: var(--minGateWidth);
+    height: var(--gateHeight);
+    position: absolute;
+    z-index: 1;
+    pointer-events: none;
+    opacity: 0.5;
+  }
+
+  .qubit-line-control {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    margin-bottom: 8px;
+  }
+
+  .qubit-line-control .title {
+    margin-left: 16px;
+  }
+
+  .add-qubit-line,
+  .remove-qubit-line {
+    border-radius: 4px;
+    padding: 8px 16px;
+    margin: 4px;
+    cursor: pointer;
+    font-size: 14px;
+    user-select: none;
+    background: var(--vscode-button-background, #007acc);
+    color: var(--vscode-button-foreground, #ffffff);
+    border: 1px solid var(--vscode-button-border, #005a9e);
+  }
+
+  .add-qubit-line:hover,
+  .remove-qubit-line:hover {
+    background: var(--vscode-button-hoverBackground, #005a9e);
+  }
+
+  .add-qubit-line:disabled,
+  .remove-qubit-line:disabled {
+    background: var(--vscode-button-secondaryBackground, #d4d4d4);
+    color: var(--vscode-disabledForeground, #a1a1a1);
+    border: 1px solid var(--vscode-button-secondaryBackground, #d4d4d4);
+    cursor: not-allowed;
+  }
 }
 
 .prompt-overlay {
@@ -361,90 +450,3 @@ text {
   line-height: 40px;
   font-size: 16px;
 }
-
-.container {
-  display: flex;
-}
-
-.edit-panel {
-  display: flex;
-  flex-direction: column;
-}
-
-.title {
-  margin: 0 0 4px;
-  font-size: 1.2rem;
-  user-select: none;
-}
-
-.block {
-  display: block;
-}
-
-.target-input,
-.controls-input,
-.parameters-input {
-  margin-bottom: 8px;
-}
-
-.toolbox-panel {
-  width: 100px;
-  min-height: var(--minToolboxHeight);
-  border-right: medium solid var(--vscode-editorWidget-border, #000000);
-}
-
-.toolbox-panel .title {
-  margin-top: 6px;
-}
-
-.toolbox-panel-svg {
-  width: -webkit-fill-available;
-  min-height: var(--minToolboxHeight);
-  height: 100%;
-}
-
-.ghost {
-  width: var(--minGateWidth);
-  height: var(--gateHeight);
-  position: absolute;
-  z-index: 1;
-  pointer-events: none;
-  opacity: 0.5;
-}
-
-.qubit-line-control {
-  display: flex;
-  justify-content: flex-start;
-  align-items: center;
-  margin-bottom: 8px;
-}
-
-.qubit-line-control .title {
-  margin-left: 16px;
-}
-
-.add-qubit-line,
-.remove-qubit-line {
-  border-radius: 4px;
-  padding: 8px 16px;
-  margin: 4px;
-  cursor: pointer;
-  font-size: 14px;
-  user-select: none;
-  background: var(--vscode-button-background, #007acc);
-  color: var(--vscode-button-foreground, #ffffff);
-  border: 1px solid var(--vscode-button-border, #005a9e);
-}
-
-.add-qubit-line:hover,
-.remove-qubit-line:hover {
-  background: var(--vscode-button-hoverBackground, #005a9e);
-}
-
-.add-qubit-line:disabled,
-.remove-qubit-line:disabled {
-  background: var(--vscode-button-secondaryBackground, #d4d4d4);
-  color: var(--vscode-disabledForeground, #a1a1a1);
-  border: 1px solid var(--vscode-button-secondaryBackground, #d4d4d4);
-  cursor: not-allowed;
-}


Test patch:
diff --git a/compiler/qsc_circuit/src/circuit/tests.rs b/compiler/qsc_circuit/src/circuit/tests.rs
index b293cfc5..c7f5b12a 100644
--- a/compiler/qsc_circuit/src/circuit/tests.rs
+++ b/compiler/qsc_circuit/src/circuit/tests.rs
@@ -142,7 +142,7 @@ fn bell() {
                 num_results: 1,
             },
         ],
-        component_grid: operation_list_to_grid(operations, 1),
+        component_grid: operation_list_to_grid(operations, 2),
     };
 
     expect![[r"
@@ -170,7 +170,7 @@ fn control_classical() {
             qubit(1),
             qubit(2),
         ],
-        component_grid: operation_list_to_grid(operations, 2),
+        component_grid: operation_list_to_grid(operations, 3),
     };
 
     expect![[r"
@@ -190,7 +190,7 @@ fn two_measurements() {
             id: 0,
             num_results: 2,
         }],
-        component_grid: operation_list_to_grid(operations, 0),
+        component_grid: operation_list_to_grid(operations, 1),
     };
 
     expect![[r"
diff --git a/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs b/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs
index 40230b58..2adb6e7f 100644
--- a/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs
+++ b/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs
@@ -458,11 +458,34 @@ fn empty_circuit() {
 }
 
 #[test]
-fn circuit_with_qubit_missing_num_results() {
+fn empty_circuit_with_qubits() {
     check(
         r#"
 {
   "componentGrid": [],
+  "qubits": [{ "id": 0 }, { "id": 1 }]
+}"#,
+        &expect![[r#"
+            /// Expects a qubit register of size 2.
+            operation Test(qs : Qubit[]) : Unit is Ctl + Adj {
+            }
+
+        "#]],
+    );
+}
+
+#[test]
+fn circuit_with_qubit_missing_num_results() {
+    check(
+        r#"
+{
+  "componentGrid": [
+    {
+      "components": [
+        { "kind": "unitary", "gate": "H", "targets": [{ "qubit": 0 }] }
+      ]
+    }
+  ],
   "qubits": [
     { "id": 0 }
   ]
@@ -473,6 +496,7 @@ fn circuit_with_qubit_missing_num_results() {
                 if Length(qs) != 1 {
                     fail "Invalid number of qubits. Operation Test expects a qubit register of size 1.";
                 }
+                H(qs[0]);
             }
 
         "#]],
@@ -505,8 +529,7 @@ fn circuit_with_ket_gates() {
                     fail "Invalid number of qubits. Operation Test expects a qubit register of size 2.";
                 }
                 Reset(qs[0]);
-                Reset(qs[1]);
-                X(qs[1]);
+                fail "Unsupported ket operation: |1〉";
             }
 
         "#]],
diff --git a/compiler/qsc_qasm/src/tests/declaration/def.rs b/compiler/qsc_qasm/src/tests/declaration/def.rs
index 96028ca0..c80d3979 100644
--- a/compiler/qsc_qasm/src/tests/declaration/def.rs
+++ b/compiler/qsc_qasm/src/tests/declaration/def.rs
@@ -234,14 +234,13 @@ fn capturing_non_const_evaluatable_external_variable_fails() {
     };
 
     expect![[r#"
-        [Qasm.Compiler.NegativeUIntValue
+        [Qasm.Lowerer.UnsupportedBinaryOp
 
-          x uint expression must evaluate to a non-negative value, but it evaluated
-          | to -3
-           ,-[Test.qasm:2:28]
+          x Shl is not supported between types Int(None, true) and UInt(None, true)
+           ,-[Test.qasm:2:23]
          1 | 
          2 |         const int a = 2 << (-3);
-           :                            ^^^^
+           :                       ^^^^^^^^^
          3 |         def f() -> int {
            `----
         , Qasm.Lowerer.ExprMustBeConst
diff --git a/compiler/qsc_qasm/src/tests/declaration/gate.rs b/compiler/qsc_qasm/src/tests/declaration/gate.rs
index a3804375..e1715edf 100644
--- a/compiler/qsc_qasm/src/tests/declaration/gate.rs
+++ b/compiler/qsc_qasm/src/tests/declaration/gate.rs
@@ -156,14 +156,13 @@ fn capturing_non_const_evaluatable_external_variable_fails() {
     };
 
     expect![[r#"
-        [Qasm.Compiler.NegativeUIntValue
+        [Qasm.Lowerer.UnsupportedBinaryOp
 
-          x uint expression must evaluate to a non-negative value, but it evaluated
-          | to -3
-           ,-[Test.qasm:2:28]
+          x Shl is not supported between types Int(None, true) and UInt(None, true)
+           ,-[Test.qasm:2:23]
          1 | 
          2 |         const int a = 2 << (-3);
-           :                            ^^^^
+           :                       ^^^^^^^^^
          3 |         gate my_gate q {
            `----
         , Qasm.Lowerer.ExprMustBeConst
diff --git a/compiler/qsc_qasm/src/tests/fuzz.rs b/compiler/qsc_qasm/src/tests/fuzz.rs
index 4f6e4397..b506740a 100644
--- a/compiler/qsc_qasm/src/tests/fuzz.rs
+++ b/compiler/qsc_qasm/src/tests/fuzz.rs
@@ -70,3 +70,10 @@ fn fuzz_2298() {
     super::compare_qasm_and_qasharp_asts(source);
     compile_qasm_best_effort(source, Profile::Unrestricted);
 }
+
+#[test]
+fn fuzz_2313() {
+    let source = r#"ctrl(π/0s)@a"#;
+    super::compare_qasm_and_qasharp_asts(source);
+    compile_qasm_best_effort(source, Profile::Unrestricted);
+}
diff --git a/compiler/qsc_qasm/src/tests/statement/const_eval.rs b/compiler/qsc_qasm/src/tests/statement/const_eval.rs
index 8451088e..2fc29c7f 100644
--- a/compiler/qsc_qasm/src/tests/statement/const_eval.rs
+++ b/compiler/qsc_qasm/src/tests/statement/const_eval.rs
@@ -76,7 +76,7 @@ fn non_const_exprs_fail_in_bitarray_size_position() {
     let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect();
     let errs_string = errs.join("\n");
     expect![[r#"
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:5:13]
@@ -96,7 +96,7 @@ fn non_const_exprs_fail_in_bitarray_size_position() {
          6 |         bit[c] r2;
            `----
 
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:6:13]
@@ -507,7 +507,7 @@ fn binary_op_shl_creg_fails() {
          5 |     
            `----
 
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:4:13]
@@ -680,7 +680,7 @@ fn binary_op_shr_creg_fails() {
          5 |     
            `----
 
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:4:13]
@@ -2025,7 +2025,7 @@ fn binary_op_err_type_fails() {
          3 |     
            `----
 
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:2:13]
@@ -2108,7 +2108,7 @@ fn fuzzer_issue_2294() {
          3 |     
            `----
 
-        Qasm.Compiler.ExprMustBeConst
+        Qasm.Lowerer.ExprMustBeConst
 
           x expression must be const
            ,-[Test.qasm:2:16]
@@ -2130,3 +2130,59 @@ fn fuzzer_issue_2294() {
     "#]]
     .assert_eq(&errs_string);
 }
+
+#[test]
+fn binary_op_with_non_supported_types_fails() {
+    let source = r#"
+        const int a = 2 / 0s;
+        def f() { a; }
+    "#;
+
+    let Err(errs) = compile_qasm_to_qsharp(source) else {
+        panic!("should have generated an error");
+    };
+    let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect();
+    let errs_string = errs.join("\n");
+    expect![[r#"
+        Qasm.Lowerer.CannotCast
+
+          x cannot cast expression of type Duration(true) to type Float(None, true)
+           ,-[Test.qasm:2:27]
+         1 | 
+         2 |         const int a = 2 / 0s;
+           :                           ^^
+         3 |         def f() { a; }
+           `----
+
+        Qasm.Lowerer.UnsupportedBinaryOp
+
+          x Div is not supported between types Float(None, true) and Duration(true)
+           ,-[Test.qasm:2:23]
+         1 | 
+         2 |         const int a = 2 / 0s;
+           :                       ^^^^^^
+         3 |         def f() { a; }
+           `----
+
+        Qasm.Lowerer.ExprMustBeConst
+
+          x a captured variable must be a const expression
+           ,-[Test.qasm:3:19]
+         2 |         const int a = 2 / 0s;
+         3 |         def f() { a; }
+           :                   ^
+         4 |     
+           `----
+
+        Qasm.Compiler.NotSupported
+
+          x timing literals are not supported
+           ,-[Test.qasm:2:27]
+         1 | 
+         2 |         const int a = 2 / 0s;
+           :                           ^^
+         3 |         def f() { a; }
+           `----
+    "#]]
+    .assert_eq(&errs_string);
+}