Repository: microsoft/qsharp
Pull request #2172 (2025-02-10 23:34:24)

Revert "disable failing vscode test"
Reverts microsoft/qsharp#2164

VS Code reverted the change that caused this test failure in the first place.


Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9a790989..05566304 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -5,23 +5,23 @@
 /compiler @idavis @minestarks @swernli
 /compiler/qsc_codegen @idavis @swernli
 /compiler/qsc_doc_gen @billti @DmitryVasilevsky @ScottCarda-MS
-/compiler/qsc_formatter @billti @ScottCarda-MS @minestarks
-/compiler/qsc_project @minestarks @sezna @swernli
+/compiler/qsc_formatter @billti @minestarks @ScottCarda-MS
+/compiler/qsc_project @idavis @minestarks @swernli
 /compiler/qsc_partial_eval @idavis @swernli
 /compiler/qsc_rca @idavis @swernli
 /compiler/qsc_rir @idavis @swernli
 /fuzz  @billti @idavis @swernli
-/katas @billti @DmitryVasilevsky @sezna @swernli
+/katas @billti @DmitryVasilevsky @swernli
 /jupyterlab @billti @idavis @minestarks
 /language_service @billti @idavis @minestarks @ScottCarda-MS
 /library @DmitryVasilevsky @swernli
-/npm @billti @minestarks @sezna
+/npm @billti @DmitryVasilevsky @minestarks
 /pip @billti @idavis @minestarks
 /playground @billti @minestarks
 /resource_estimator @billti @ivanbasov @swernli
-/samples @DmitryVasilevsky @sezna @swernli
-/vscode @billti @idavis @minestarks @sezna
-/wasm @billti @sezna @swernli
+/samples @DmitryVasilevsky @minestarks @swernli
+/vscode @billti @idavis @minestarks
+/wasm @billti @idavis @swernli
 
 /Cargo.lock @idavis @minestarks @swernli
 /Cargo.toml @idavis @minestarks @swernli
diff --git a/compiler/qsc_circuit/src/circuit.rs b/compiler/qsc_circuit/src/circuit.rs
index 508845f3..da101293 100644
--- a/compiler/qsc_circuit/src/circuit.rs
+++ b/compiler/qsc_circuit/src/circuit.rs
@@ -4,12 +4,12 @@
 #[cfg(test)]
 mod tests;
 
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 use serde::Serialize;
-use std::{fmt::Display, fmt::Write, ops::Not, vec};
+use std::{cmp, fmt::Display, fmt::Write, ops::Not, vec};
 
 /// Representation of a quantum circuit.
-/// Implementation of <https://github.com/microsoft/quantum-viz.js/wiki/API-schema-reference>
+/// Implementation of `CircuitData` type from `qsharp-lang` npm package.
 #[derive(Clone, Serialize, Default, Debug, PartialEq)]
 pub struct Circuit {
     pub operations: Vec<Operation>,
@@ -96,7 +96,6 @@ impl Config {
 }
 
 type ObjectsByColumn = FxHashMap<usize, CircuitObject>;
-type ColumnWidthsByColumn = FxHashMap<usize, usize>;
 
 struct Row {
     wire: Wire,
@@ -110,6 +109,8 @@ enum Wire {
 }
 
 enum CircuitObject {
+    Blank,
+    Wire,
     WireCross,
     WireStart,
     DashedCross,
@@ -178,38 +179,30 @@ impl Row {
         self.next_column = column + 1;
     }
 
-    fn fmt(
-        &self,
-        f: &mut std::fmt::Formatter<'_>,
-        column_widths: &ColumnWidthsByColumn,
-        end_column: usize,
-    ) -> std::fmt::Result {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, columns: &[Column]) -> std::fmt::Result {
         // Temporary string so we can trim whitespace at the end
         let mut s = String::new();
         match &self.wire {
             Wire::Qubit { q_id: label } => {
                 s.write_str(&fmt_qubit_label(*label))?;
-                for column in 1..end_column {
-                    let val = self.objects.get(&column);
-                    let column_width = *column_widths.get(&column).unwrap_or(&MIN_COLUMN_WIDTH);
-                    if let Some(v) = val {
-                        s.write_str(&fmt_qubit_circuit_object(v, column_width))?;
-                    } else {
-                        s.write_str(&get_qubit_wire(column_width))?;
-                    }
+                for (column_index, column) in columns.iter().enumerate().skip(1) {
+                    let val = self.objects.get(&column_index);
+                    let object = val.unwrap_or(&CircuitObject::Wire);
+
+                    s.write_str(&column.fmt_qubit_circuit_object(object))?;
                 }
             }
             Wire::Classical { start_column } => {
-                for column in 0..end_column {
-                    let val = self.objects.get(&column);
-                    let column_width = *column_widths.get(&column).unwrap_or(&MIN_COLUMN_WIDTH);
-                    if let Some(v) = val {
-                        s.write_str(&fmt_classical_circuit_object(v, column_width))?;
-                    } else if start_column.map_or(false, |s| column > s) {
-                        s.write_str(&get_classical_wire(column_width))?;
-                    } else {
-                        s.write_str(&get_blank(column_width))?;
-                    }
+                for (column_index, column) in columns.iter().enumerate() {
+                    let val = self.objects.get(&column_index);
+
+                    let object = match (val, start_column) {
+                        (Some(v), _) => v,
+                        (None, Some(s)) if column_index > *s => &CircuitObject::Wire,
+                        _ => &CircuitObject::Blank,
+                    };
+
+                    s.write_str(&column.fmt_classical_circuit_object(object))?;
                 }
             }
         }
@@ -220,100 +213,101 @@ impl Row {
 
 const MIN_COLUMN_WIDTH: usize = 7;
 
-/// "───────"
-fn get_qubit_wire(column_width: usize) -> String {
-    "─".repeat(column_width)
-}
-
-/// "═══════"
-fn get_classical_wire(column_width: usize) -> String {
-    "═".repeat(column_width)
-}
-
-/// "───┼───"
-fn get_qubit_wire_cross(column_width: usize) -> String {
-    let half_width = "─".repeat(column_width / 2);
-    format!("{}┼{}", half_width, half_width)
-}
-
-/// "═══╪═══"
-fn get_classical_wire_cross(column_width: usize) -> String {
-    let half_width = "═".repeat(column_width / 2);
-    format!("{}╪{}", half_width, half_width)
-}
+const QUBIT_WIRE: [char; 3] = ['─', '─', '─']; // "───────"
+const CLASSICAL_WIRE: [char; 3] = ['═', '═', '═']; // "═══════"
+const QUBIT_WIRE_CROSS: [char; 3] = ['─', '┼', '─']; // "───┼───"
+const CLASSICAL_WIRE_CROSS: [char; 3] = ['═', '╪', '═']; // "═══╪═══"
+const CLASSICAL_WIRE_START: [char; 3] = [' ', '╘', '═']; // "   ╘═══"
+const QUBIT_WIRE_DASHED_CROSS: [char; 3] = ['─', '┆', '─']; // "───┆───"
+const CLASSICAL_WIRE_DASHED_CROSS: [char; 3] = ['═', '┆', '═']; // "═══┆═══"
+const VERTICAL_DASHED: [char; 3] = [' ', '┆', ' ']; // "   │   "
+const VERTICAL: [char; 3] = [' ', '│', ' ']; // "   ┆   "
+const BLANK: [char; 3] = [' ', ' ', ' ']; // "       "
 
-/// "   ╘═══"
-fn get_classical_wire_start(column_width: usize) -> String {
-    let first_half_width = " ".repeat(column_width / 2);
-    let second_half_width = "═".repeat(column_width / 2);
-    format!("{}╘{}", first_half_width, second_half_width)
+/// "q_0  "
+#[allow(clippy::doc_markdown)]
+fn fmt_qubit_label(id: usize) -> String {
+    let rest = MIN_COLUMN_WIDTH - 2;
+    format!("q_{id: <rest$}")
 }
 
-/// "───┆───"
-fn get_qubit_wire_dashed_cross(column_width: usize) -> String {
-    let half_width = "─".repeat(column_width / 2);
-    format!("{}┆{}", half_width, half_width)
+struct Column {
+    column_width: usize,
 }
 
-/// "═══┆═══"
-fn get_classical_wire_dashed_cross(column_width: usize) -> String {
-    let half_width = "═".repeat(column_width / 2);
-    format!("{}┆{}", half_width, half_width)
+impl Default for Column {
+    fn default() -> Self {
+        Self {
+            column_width: MIN_COLUMN_WIDTH,
+        }
+    }
 }
 
-/// "   │   "
-fn get_vertical(column_width: usize) -> String {
-    let half_width = " ".repeat(column_width / 2);
-    format!("{}│{}", half_width, half_width)
-}
+impl Column {
+    fn new(column_width: usize) -> Self {
+        // Column widths should be odd numbers for this struct to work well
+        let odd_column_width = column_width | 1;
+        Self {
+            column_width: odd_column_width,
+        }
+    }
 
-/// "   ┆   "
-fn get_vertical_dashed(column_width: usize) -> String {
-    let half_width = " ".repeat(column_width / 2);
-    format!("{}┆{}", half_width, half_width)
-}
+    /// "── A ──"
+    fn fmt_on_qubit_wire(&self, obj: &str) -> String {
+        let column_width = self.column_width;
+        format!("{:─^column_width$}", format!(" {obj} "))
+    }
 
-/// "       "
-fn get_blank(column_width: usize) -> String {
-    " ".repeat(column_width)
-}
+    /// "══ A ══"
+    fn fmt_on_classical_wire(&self, obj: &str) -> String {
+        let column_width = self.column_width;
+        format!("{:═^column_width$}", format!(" {obj} "))
+    }
 
-/// "q_0  "
-#[allow(clippy::doc_markdown)]
-fn fmt_qubit_label(id: usize) -> String {
-    let rest = MIN_COLUMN_WIDTH - 2;
-    format!("q_{id: <rest$}")
-}
+    fn expand_template(&self, template: &[char; 3]) -> String {
+        let half_width = self.column_width / 2;
+        let left = template[0].to_string().repeat(half_width);
+        let right = template[2].to_string().repeat(half_width);
 
-/// "── A ──"
-fn fmt_on_qubit_wire(obj: &str, column_width: usize) -> String {
-    format!("{:─^column_width$}", format!(" {obj} "))
-}
+        format!("{left}{}{right}", template[1])
+    }
 
-/// "══ A ══"
-fn fmt_on_classical_wire(obj: &str, column_width: usize) -> String {
-    format!("{:═^column_width$}", format!(" {obj} "))
-}
+    fn fmt_classical_circuit_object(&self, circuit_object: &CircuitObject) -> String {
+        if let CircuitObject::Object(label) = circuit_object {
+            return self.fmt_on_classical_wire(label.as_str());
+        }
 
-fn fmt_classical_circuit_object(circuit_object: &CircuitObject, column_width: usize) -> String {
-    match circuit_object {
-        CircuitObject::WireCross => get_classical_wire_cross(column_width),
-        CircuitObject::WireStart => get_classical_wire_start(column_width),
-        CircuitObject::DashedCross => get_classical_wire_dashed_cross(column_width),
-        CircuitObject::Vertical => get_vertical(column_width),
-        CircuitObject::VerticalDashed => get_vertical_dashed(column_width),
-        CircuitObject::Object(label) => fmt_on_classical_wire(label.as_str(), column_width),
+        let template = match circuit_object {
+            CircuitObject::Blank => BLANK,
+            CircuitObject::Wire => CLASSICAL_WIRE,
+            CircuitObject::WireCross => CLASSICAL_WIRE_CROSS,
+            CircuitObject::WireStart => CLASSICAL_WIRE_START,
+            CircuitObject::DashedCross => CLASSICAL_WIRE_DASHED_CROSS,
+            CircuitObject::Vertical => VERTICAL,
+            CircuitObject::VerticalDashed => VERTICAL_DASHED,
+            CircuitObject::Object(_) => unreachable!("This case is covered in the early return."),
+        };
+
+        self.expand_template(&template)
     }
-}
 
-fn fmt_qubit_circuit_object(circuit_object: &CircuitObject, column_width: usize) -> String {
-    match circuit_object {
-        CircuitObject::WireCross => get_qubit_wire_cross(column_width),
-        CircuitObject::WireStart => get_blank(column_width), // This should never happen
-        CircuitObject::DashedCross => get_qubit_wire_dashed_cross(column_width),
-        CircuitObject::Vertical => get_vertical(column_width),
-        CircuitObject::VerticalDashed => get_vertical_dashed(column_width),
-        CircuitObject::Object(label) => fmt_on_qubit_wire(label.as_str(), column_width),
+    fn fmt_qubit_circuit_object(&self, circuit_object: &CircuitObject) -> String {
+        if let CircuitObject::Object(label) = circuit_object {
+            return self.fmt_on_qubit_wire(label.as_str());
+        }
+
+        let template = match circuit_object {
+            CircuitObject::Blank => BLANK,
+            CircuitObject::Wire => QUBIT_WIRE,
+            CircuitObject::WireCross => QUBIT_WIRE_CROSS,
+            CircuitObject::WireStart => BLANK, // This should never happen
+            CircuitObject::DashedCross => QUBIT_WIRE_DASHED_CROSS,
+            CircuitObject::Vertical => VERTICAL,
+            CircuitObject::VerticalDashed => VERTICAL_DASHED,
+            CircuitObject::Object(_) => unreachable!("This case is covered in the early return."),
+        };
+
+        self.expand_template(&template)
     }
 }
 
@@ -325,6 +319,27 @@ impl Display for Circuit {
         // to row in the diagram
         let mut register_to_row = FxHashMap::default();
 
+        // Keep track of which qubits have the qubit after them in the same multi-qubit operation,
+        // because those qubits need to get a gap row below them.
+        let mut qubits_with_gap_row_below = FxHashSet::default();
+
+        for operation in self.operations.iter() {
+            for target in operation.targets.iter() {
+                let qubit = target.q_id;
+
+                if qubits_with_gap_row_below.contains(&qubit) {
+                    continue;
+                }
+
+                let next_qubit = qubit + 1;
+
+                // Check if the next qubit is also in this operation.
+                if operation.targets.iter().any(|t| t.q_id == next_qubit) {
+                    qubits_with_gap_row_below.insert(qubit);
+                }
+            }
+        }
+
         // Initialize all qubit and classical wires
         for q in &self.qubits {
             rows.push(Row {
@@ -335,7 +350,16 @@ impl Display for Circuit {
 
             register_to_row.insert((q.id, None), rows.len() - 1);
 
-            for i in 0..q.num_children {
+            // If this qubit has no children, but it is in a multi-qubit operation with
+            // the next qubit, we add an empty row to make room for the vertical connector.
+            // We can just use a classical wire type for this row since the wire won't actually be rendered.
+            let extra_rows = if qubits_with_gap_row_below.contains(&q.id) {
+                cmp::max(1, q.num_children)
+            } else {
+                q.num_children
+            };
+
+            for i in 0..extra_rows {
                 rows.push(Row {
                     wire: Wire::Classical { start_column: None },
                     objects: FxHashMap::default(),
@@ -431,14 +455,13 @@ impl Display for Circuit {
 
         // To be able to fit long-named operations, we calculate the required width for each column,
         // based on the maximum length needed for gates, where a gate X is printed as "- X -".
-        let column_widths = (0..end_column)
+        let columns = (0..end_column)
             .map(|column| {
-                (
-                    column,
+                Column::new(
                     rows.iter()
                         .filter_map(|row| row.objects.get(&column))
                         .filter_map(|object| match object {
-                            CircuitObject::Object(string) => Some((string.len() + 4) | 1), // Column lengths need to be odd numbers
+                            CircuitObject::Object(string) => Some(string.len() + 4),
                             _ => None,
                         })
                         .chain(std::iter::once(MIN_COLUMN_WIDTH))
@@ -446,11 +469,11 @@ impl Display for Circuit {
                         .unwrap(),
                 )
             })
-            .collect::<ColumnWidthsByColumn>();
+            .collect::<Vec<_>>();
 
         // Draw the diagram
         for row in rows {
-            row.fmt(f, &column_widths, end_column)?;
+            row.fmt(f, &columns)?;
         }
 
         Ok(())
diff --git a/compiler/qsc_circuit/src/operations.rs b/compiler/qsc_circuit/src/operations.rs
index c1562405..af7e22a0 100644
--- a/compiler/qsc_circuit/src/operations.rs
+++ b/compiler/qsc_circuit/src/operations.rs
@@ -33,7 +33,7 @@ pub enum Error {
 /// If the item is not a callable, returns `None`.
 /// If the callable takes any non-qubit parameters, returns `None`.
 ///
-/// If the callable only takes qubit parameters, (including qubit arrays):
+/// If the callable only takes qubit parameters (including qubit arrays) or no parameters:
 ///
 /// The first element of the return tuple is a vector,
 /// where each element corresponds to a parameter, and the
@@ -47,6 +47,11 @@ pub enum Error {
 #[must_use]
 pub fn qubit_param_info(item: &Item) -> Option<(Vec<u32>, u32)> {
     if let ItemKind::Callable(decl) = &item.kind {
+        if decl.input.ty == Ty::UNIT {
+            // Support no parameters by allocating 0 qubits.
+            return Some((vec![], 0));
+        }
+
         let (qubit_param_dimensions, total_num_qubits) = get_qubit_param_info(&decl.input.ty);
 
         if !qubit_param_dimensions.is_empty() {
diff --git a/compiler/qsc_frontend/src/typeck/rules.rs b/compiler/qsc_frontend/src/typeck/rules.rs
index 2afeaaf8..4e4c906c 100644
--- a/compiler/qsc_frontend/src/typeck/rules.rs
+++ b/compiler/qsc_frontend/src/typeck/rules.rs
@@ -399,12 +399,8 @@ impl<'a> Context<'a> {
                     .take()
                     .expect("return type should be present");
                 self.return_ty = prev_ret_ty;
-                if !body_partial.diverges {
-                    // Only when the type of the body converges do we need to unify with the inferred output type.
-                    // Otherwise we'd get spurious errors from lambdas that use explicit return-expr rather than implicit.
-                    self.inferrer
-                        .eq(body.span, output_ty.clone(), body_partial.ty);
-                }
+                self.inferrer
+                    .eq(body.span, body_partial.ty, output_ty.clone());
                 converge(Ty::Arrow(Box::new(Arrow {
                     kind: convert::callable_kind_from_ast(*kind),
                     input: Box::new(input),
diff --git a/katas/README.md b/katas/README.md
index 95d6c0bc..54c740d5 100644
--- a/katas/README.md
+++ b/katas/README.md
@@ -8,7 +8,7 @@ A Kata is a top-level container of educational items which are used to explain a
 
 ## Run Katas Online
 
-Visit [Learn with Azure Quantum katas](https://quantum.microsoft.com/experience/quantum-katas) to try the new online Azure Quantum katas experience, with integrated assistance from Copilot in Azure Quantum.
+Visit [Learn with Microsoft Quantum katas](https://quantum.microsoft.com/experience/quantum-katas) to try the new online Microsoft Quantum katas experience, with integrated assistance from Copilot in Microsoft Quantum.
 
 ## Build Katas Locally
 
diff --git a/katas/content/complex_arithmetic/powers_of_i/index.md b/katas/content/complex_arithmetic/powers_of_i/index.md
index d83ca757..5a3e7dbc 100644
--- a/katas/content/complex_arithmetic/powers_of_i/index.md
+++ b/katas/content/complex_arithmetic/powers_of_i/index.md
@@ -1,3 +1,3 @@
 **Input:** An even integer $n$ (can be negative).
 
-**Goal:** Return the $n$-th power of $i$ ($i^n$).
+**Goal:** Return $i^n$, that is, the $n$-th power of $i$.
diff --git a/katas/content/getting_started/index.md b/katas/content/getting_started/index.md
index 88c85803..f10590eb 100644
--- a/katas/content/getting_started/index.md
+++ b/katas/content/getting_started/index.md
@@ -25,7 +25,7 @@ _The Japanese word for "form", a pattern of learning and practicing new skills._
         </tr>
         <tr class="transparent" style="border:none;">
             <td style="border:none;">
-                Quantum katas are self-paced learning tutorials assisted by Copilot in Azure Quantum.
+                Quantum katas are self-paced learning tutorials assisted by Copilot in Microsoft Quantum.
             </td>
         </tr>
         <tr class="transparent" style="border:none;">
diff --git a/language_service/src/code_lens.rs b/language_service/src/code_lens.rs
index bed22671..e954b402 100644
--- a/language_service/src/code_lens.rs
+++ b/language_service/src/code_lens.rs
@@ -11,7 +11,7 @@ use crate::{
 };
 use qsc::{
     circuit::qubit_param_info,
-    hir::{Expr, ExprKind, ItemId, ItemKind, LocalItemId, Package, Res},
+    hir::{ty::Ty, CallableKind, ItemKind},
     line_column::Encoding,
 };
 
@@ -29,95 +29,72 @@ pub(crate) fn get_code_lenses(
     let package = &user_unit.package;
     let source_span = compilation.package_span_of_source(source_name);
 
-    let entry_item_id = entry_callable(package);
+    package
+        .items
+        .iter()
+        .fold(Vec::new(), |mut accum, (_, item)| {
+            if source_span.contains(item.span.lo) {
+                if let ItemKind::Callable(decl) = &item.kind {
+                    if let Some(ItemKind::Namespace(ns, _)) = item
+                        .parent
+                        .and_then(|parent_id| package.items.get(parent_id))
+                        .map(|parent| &parent.kind)
+                    {
+                        let namespace = ns.name();
+                        let range = into_range(position_encoding, decl.span, &user_unit.sources);
+                        let name = decl.name.name.clone();
 
-    // Get callables in the current source file.
-    let callables = package.items.iter().filter_map(|(item_id, item)| {
-        if source_span.contains(item.span.lo) {
-            if let ItemKind::Callable(decl) = &item.kind {
-                if let Some(ItemKind::Namespace(ns, _)) = item
-                    .parent
-                    .and_then(|parent_id| package.items.get(parent_id))
-                    .map(|parent| &parent.kind)
-                {
-                    let namespace = ns.name();
-                    let range = into_range(position_encoding, decl.span, &user_unit.sources);
-                    let name = decl.name.name.clone();
+                        if decl.input.ty == Ty::UNIT {
+                            // For a callable that takes no input, always show all lenses except for circuit.
+                            let expr = format!("{namespace}.{name}()");
+                            accum = accum
+                                .into_iter()
+                                .chain([
+                                    CodeLens {
+                                        range,
+                                        command: CodeLensCommand::Run(expr.clone()),
+                                    },
+                                    CodeLens {
+                                        range,
+                                        command: CodeLensCommand::Histogram(expr.clone()),
+                                    },
+                                    CodeLens {
+                                        range,
+                                        command: CodeLensCommand::Estimate(expr.clone()),
+                                    },
+                                    CodeLens {
+                                        range,
+                                        command: CodeLensCommand::Debug(expr),
+                                    },
+                                ])
+                                .collect();
+                        }
+                        if decl.kind == CallableKind::Operation {
+                            // If this is either an operation that takes no arguments or one that takes only qubit arguments,
+                            // show the circuit lens.
+                            if decl.input.ty == Ty::UNIT {
+                                accum.push(CodeLens {
+                                    range,
+                                    command: CodeLensCommand::Circuit(OperationInfo {
+                                        operation: format!("{namespace}.{name}"),
+                                        total_num_qubits: 0,
+                                    }),
+                                });
+                            } else if let Some((_, total_num_qubits)) = qubit_param_info(item) {
+                                accum.push(CodeLens {
+                                    range,
+                                    command: CodeLensCommand::Circuit(OperationInfo {
+                                        operation: format!("{namespace}.{name}"),
+                                        total_num_qubits,
+                                    }),
+                                });
+                            }
+                        }
 
-                    return Some((item, range, namespace, name, Some(item_id) == entry_item_id));
+                        return accum;
+                    }
                 }
             }
-        }
-        None
-    });
-
-    callables
-        .flat_map(|(item, range, namespace, name, is_entry_point)| {
-            if is_entry_point {
-                vec![
-                    CodeLens {
-                        range,
-                        command: CodeLensCommand::Run,
-                    },
-                    CodeLens {
-                        range,
-                        command: CodeLensCommand::Histogram,
-                    },
-                    CodeLens {
-                        range,
-                        command: CodeLensCommand::Estimate,
-                    },
-                    CodeLens {
-                        range,
-                        command: CodeLensCommand::Debug,
-                    },
-                    CodeLens {
-                        range,
-                        command: CodeLensCommand::Circuit(None),
-                    },
-                ]
-            } else {
-                if let Some((_, total_num_qubits)) = qubit_param_info(item) {
-                    return vec![CodeLens {
-                        range,
-                        command: CodeLensCommand::Circuit(Some(OperationInfo {
-                            operation: format!("{namespace}.{name}"),
-                            total_num_qubits,
-                        })),
-                    }];
-                }
-                vec![]
-            }
+            accum
         })
-        .collect()
-}
-
-/// Uses the entry expression in the package to find the
-/// entrypoint callable item id. The entry expression has to
-/// be a call to a parameterless operation or function. This is the
-/// specific pattern generated by the "generate entry expression" pass.
-/// In practice, this callable will be the one tagged with the
-/// `@EntryPoint()` attribute or named "Main".
-fn entry_callable(package: &Package) -> Option<LocalItemId> {
-    if let Some(Expr {
-        kind: ExprKind::Call(callee, args),
-        ..
-    }) = &package.entry
-    {
-        if let ExprKind::Tuple(args) = &args.kind {
-            if args.is_empty() {
-                if let ExprKind::Var(
-                    Res::Item(ItemId {
-                        package: None,
-                        item,
-                    }),
-                    ..,
-                ) = callee.kind
-                {
-                    return Some(item);
-                }
-            }
-        }
-    }
-    None
 }
diff --git a/language_service/src/protocol.rs b/language_service/src/protocol.rs
index 8a6c2f34..978da9d4 100644
--- a/language_service/src/protocol.rs
+++ b/language_service/src/protocol.rs
@@ -198,11 +198,11 @@ pub struct CodeLens {
 
 #[derive(Debug)]
 pub enum CodeLensCommand {
-    Histogram,
-    Debug,
-    Run,
-    Estimate,
-    Circuit(Option<OperationInfo>),
+    Histogram(String),
+    Debug(String),
+    Run(String),
+    Estimate(String),
+    Circuit(OperationInfo),
 }
 
 #[derive(Debug)]
diff --git a/library/chemistry/src/JordanWigner/JordanWignerOptimizedBlockEncoding.qs b/library/chemistry/src/JordanWigner/JordanWignerOptimizedBlockEncoding.qs
index 545e5998..0f1da5fb 100644
--- a/library/chemistry/src/JordanWigner/JordanWignerOptimizedBlockEncoding.qs
+++ b/library/chemistry/src/JordanWigner/JordanWignerOptimizedBlockEncoding.qs
@@ -8,9 +8,6 @@ export
     MixedStatePreparation,
     MixedStatePreparationRequirements,
     PurifiedMixedState,
-    ObliviousAmplitudeAmplificationFromStatePreparation,
-    ObliviousAmplitudeAmplificationFromPartialReflections,
-    ApplyObliviousAmplitudeAmplification,
     PurifiedMixedStateRequirements,
     BlockEncodingByLCU,
     QuantumWalkByQubitization,
@@ -27,6 +24,7 @@ import Std.Math.*;
 import Std.Convert.IntAsDouble;
 import Std.Arithmetic.ApplyIfGreaterLE;
 import Std.StatePreparation.PreparePureStateD;
+import Std.StatePreparation.PrepareUniformSuperposition;
 import Std.Diagnostics.Fact;
 import Utils.GeneratorIndex;
 import Utils.GeneratorSystem;
@@ -628,207 +626,6 @@ operation PrepareQuantumROMState(
     }
 }
 
-/// # Summary
-/// Creates a uniform superposition over states that encode 0 through `nIndices - 1`.
-///
-/// # Description
-///
-/// This operation can be described by a unitary matrix $U$ that creates
-/// a uniform superposition over all number states
-/// $0$ to $M-1$, given an input state $\ket{0\cdots 0}$. In other words,
-/// $$
-/// \begin{align}
-///     U \ket{0} = \frac{1}{\sqrt{M}} \sum_{j=0}^{M - 1} \ket{j}.
-/// \end{align}
-/// $$.
-///
-/// # Input
-/// ## nIndices
-/// The desired number of states $M$ in the uniform superposition.
-/// ## indexRegister
-/// The qubit register that stores the number states in `LittleEndian` format.
-/// This register must be able to store the number $M-1$, and is assumed to be
-/// initialized in the state $\ket{0\cdots 0}$.
-///
-/// # Remarks
-/// The operation is adjointable, but requires that `indexRegister` is in a uniform
-/// superposition over the first `nIndices` basis states in that case.
-///
-/// # Example
-/// The following example prepares the state $\frac{1}{\sqrt{6}}\sum_{j=0}^{5}\ket{j}$
-/// on $3$ qubits.
-/// ``` Q#
-/// let nIndices = 6;
-/// using(indexRegister = Qubit[3]) {
-///     PrepareUniformSuperposition(nIndices, LittleEndian(indexRegister));
-///     // ...
-/// }
-/// ```
-operation PrepareUniformSuperposition(nIndices : Int, indexRegister : Qubit[]) : Unit is Adj + Ctl {
-    if nIndices == 0 {
-        fail "Cannot prepare uniform superposition over 0 basis states.";
-    } elif nIndices == 1 {
-        // Superposition over one state, so do nothing.
-    } elif nIndices == 2 {
-        H(indexRegister[0]);
-    } else {
-        let nQubits = BitSizeI(nIndices - 1);
-        if nQubits > Length(indexRegister) {
-            fail $"Cannot prepare uniform superposition over {nIndices} states as it is larger than the qubit register.";
-        }
-
-        use flagQubit = Qubit[3];
-        let targetQubits = indexRegister[0..nQubits - 1];
-        let qubits = flagQubit + targetQubits;
-        let stateOracle = PrepareUniformSuperpositionOracle(nIndices, nQubits, 0, _);
-
-        let phases = ([0.0, PI()], [PI(), 0.0]);
-
-        ObliviousAmplitudeAmplificationFromStatePreparation(
-            phases,
-            stateOracle,
-            (qs0, qs1) => I(qs0[0]),
-            0
-        )(qubits, []);
-
-
-        ApplyToEachCA(X, flagQubit);
-    }
-}
-
-function ObliviousAmplitudeAmplificationFromStatePreparation(
-    phases : (Double[], Double[]),
-    startStateOracle : Qubit[] => Unit is Adj + Ctl,
-    signalOracle : (Qubit[], Qubit[]) => Unit is Adj + Ctl,
-    idxFlagQubit : Int
-) : (Qubit[], Qubit[]) => Unit is Adj + Ctl {
-    let startStateReflection = (phase, qs) => {
-        within {
-            ApplyToEachCA(X, qs);
-        } apply {
-            Controlled R1(Rest(qs), (phase, qs[0]));
-        }
-    };
-    let targetStateReflection = (phase, qs) => R1(phase, qs[idxFlagQubit]);
-    let obliviousSignalOracle = (qs0, qs1) => { startStateOracle(qs0); signalOracle(qs0, qs1); };
-    return ObliviousAmplitudeAmplificationFromPartialReflections(
-        phases,
-        startStateReflection,
-        targetStateReflection,
-        obliviousSignalOracle
-    );
-}
-
-function ObliviousAmplitudeAmplificationFromPartialReflections(
-    phases : (Double[], Double[]),
-    startStateReflection : (Double, Qubit[]) => Unit is Adj + Ctl,
-    targetStateReflection : (Double, Qubit[]) => Unit is Adj + Ctl,
-    signalOracle : (Qubit[], Qubit[]) => Unit is Adj + Ctl
-) : (Qubit[], Qubit[]) => Unit is Adj + Ctl {
-    return ApplyObliviousAmplitudeAmplification(
-        phases,
-        startStateReflection,
-        targetStateReflection,
-        signalOracle,
-        _,
-        _
-    );
-}
-
-/// # Summary
-/// Oblivious amplitude amplification by specifying partial reflections.
-///
-/// # Input
-/// ## phases
-/// Phases of partial reflections
-/// ## startStateReflection
-/// Reflection operator about start state of auxiliary register
-/// ## targetStateReflection
-/// Reflection operator about target state of auxiliary register
-/// ## signalOracle
-/// Unitary oracle $O$ of type `ObliviousOracle` that acts jointly on the
-/// auxiliary and system registers.
-/// ## auxiliaryRegister
-/// Auxiliary register
-/// ## systemRegister
-/// System register
-///
-/// # Remarks
-/// Given a particular auxiliary start state $\ket{\text{start}}\_a$, a
-/// particular auxiliary target state $\ket{\text{target}}\_a$, and any
-/// system state $\ket{\psi}\_s$, suppose that
-/// \begin{align}
-/// O\ket{\text{start}}\_a\ket{\psi}\_s= \lambda\ket{\text{target}}\_a U \ket{\psi}\_s + \sqrt{1-|\lambda|^2}\ket{\text{target}^\perp}\_a\cdots
-/// \end{align}
-/// for some unitary $U$.
-/// By a sequence of reflections about the start and target states on the
-/// auxiliary register interleaved by applications of `signalOracle` and its
-/// adjoint, the success probability of applying $U$ may be altered.
-///
-/// In most cases, `auxiliaryRegister` is initialized in the state $\ket{\text{start}}\_a$.
-///
-/// # References
-/// - See [*D.W. Berry, A.M. Childs, R. Cleve, R. Kothari, R.D. Somma*](https://arxiv.org/abs/1312.1414)
-/// for the standard version.
-/// - See [*G.H. Low, I.L. Chuang*](https://arxiv.org/abs/1610.06546)
-/// for a generalization to partial reflections.
-operation ApplyObliviousAmplitudeAmplification(
-    phases : (Double[], Double[]),
-    startStateReflection : (Double, Qubit[]) => Unit is Adj + Ctl,
-    targetStateReflection : (Double, Qubit[]) => Unit is Adj + Ctl,
-    signalOracle : (Qubit[], Qubit[]) => Unit is Adj + Ctl,
-    auxiliaryRegister : Qubit[],
-    systemRegister : Qubit[]
-) : Unit is Adj + Ctl {
-    let (aboutStart, aboutTarget) = phases;
-    Fact(Length(aboutStart) == Length(aboutTarget), "number of phases about start and target state must be equal");
-    let numPhases = Length(aboutStart);
-
-    for idx in 0..numPhases-1 {
-        if aboutStart[idx] != 0.0 {
-            startStateReflection(aboutStart[idx], auxiliaryRegister);
-        }
-
-        if aboutTarget[idx] != 0.0 {
-            // In the last iteration we do not need to apply `Adjoint signalOracle`
-            if idx == numPhases - 1 {
-                signalOracle(auxiliaryRegister, systemRegister);
-                targetStateReflection(aboutTarget[idx], auxiliaryRegister);
-            } else {
-                within {
-                    signalOracle(auxiliaryRegister, systemRegister);
-                } apply {
-                    targetStateReflection(aboutTarget[idx], auxiliaryRegister);
-                }
-            }
-        }
-    }
-
-    // We do need one more `signalOracle` call, if the last phase about the target state was 0.0
-    if numPhases == 0 or aboutTarget[numPhases - 1] == 0.0 {
-        signalOracle(auxiliaryRegister, systemRegister);
-    }
-}
-
-operation PrepareUniformSuperpositionOracle(nIndices : Int, nQubits : Int, idxFlag : Int, qubits : Qubit[]) : Unit is Adj + Ctl {
-    let targetQubits = qubits[3...];
-    let flagQubit = qubits[0];
-    let auxillaryQubits = qubits[1..2];
-    let theta = ArcSin(Sqrt(IntAsDouble(2^nQubits) / IntAsDouble(nIndices)) * Sin(PI() / 6.0));
-
-    ApplyToEachCA(H, targetQubits);
-    use compareQubits = Qubit[nQubits] {
-        within {
-            ApplyXorInPlace(nIndices - 1, compareQubits);
-        } apply {
-            ApplyIfGreaterLE(X, targetQubits, compareQubits, auxillaryQubits[0]);
-            X(auxillaryQubits[0]);
-        }
-    }
-    Ry(2.0 * theta, auxillaryQubits[1]);
-    (Controlled X)(auxillaryQubits, flagQubit);
-}
-
 // Classical processing
 // This discretizes the coefficients such that
 // |coefficient[i] * oneNorm - discretizedCoefficient[i] * discretizedOneNorm| * nCoeffs <= 2^{1-bitsPrecision}.
diff --git a/library/fixed_point/src/Facts.qs b/library/fixed_point/src/Facts.qs
index 3f01db28..1bc83c6d 100644
--- a/library/fixed_point/src/Facts.qs
+++ b/library/fixed_point/src/Facts.qs
@@ -2,8 +2,8 @@
 // Licensed under the MIT License.
 
 import Types.FixedPoint;
-import Std.Arrays.IsEmpty, Std.Arrays.Rest;
-import Std.Diagnostics.Fact, Std.Diagnostics.CheckAllZero;
+import Std.Diagnostics.Fact;
+import Std.Arrays.IsEmpty, Std.Arrays.Rest, Std.Arrays.Unzipped;
 
 /// # Summary
 /// Asserts that a quantum fixed-point number is
@@ -12,7 +12,9 @@ import Std.Diagnostics.Fact, Std.Diagnostics.CheckAllZero;
 /// # Description
 /// This assertion succeeds when all qubits are in state $\ket{0}$,
 /// representing that the register encodes the fixed-point number $0.0$.
+@Config(Unrestricted)
 operation AssertAllZeroFxP(fp : FixedPoint) : Unit {
+    import Std.Diagnostics.CheckAllZero;
     Fact(CheckAllZero(fp::Register), "Quantum fixed-point number was not zero.");
 }
 
@@ -25,6 +27,7 @@ operation AssertAllZeroFxP(fp : FixedPoint) : Unit {
 /// Array of quantum fixed-point numbers that will be checked for
 /// compatibility (using assertions).
 function AssertFormatsAreIdenticalFxP(fixedPoints : FixedPoint[]) : Unit {
+
     if IsEmpty(fixedPoints) {
         return ();
     }
diff --git a/library/fixed_point/src/Main.qs b/library/fixed_point/src/Main.qs
index 845e7c59..93521f3b 100644
--- a/library/fixed_point/src/Main.qs
+++ b/library/fixed_point/src/Main.qs
@@ -1,4 +1,10 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-export Init.PrepareFxP as PrepareFxP, Measurement.MeasureFxP as MeasureFxP, Reciprocal.ComputeReciprocalFxP as ComputeReciprocalFxP, Types.FixedPoint as FixedPoint;
+export Init.PrepareFxP as PrepareFxP, Types.FixedPoint as FixedPoint;
+
+@Config(Unrestricted)
+export Reciprocal.ComputeReciprocalFxP as ComputeReciprocalFxP;
+
+@Config(FloatingPointComputations)
+export Measurement.MeasureFxP as MeasureFxP;
diff --git a/library/fixed_point/src/Measurement.qs b/library/fixed_point/src/Measurement.qs
index 1e1c42f4..c8a6fb5d 100644
--- a/library/fixed_point/src/Measurement.qs
+++ b/library/fixed_point/src/Measurement.qs
@@ -14,6 +14,7 @@ import Std.Convert.ResultArrayAsBoolArray;
 /// # Input
 /// ## fp
 /// Fixed-point number to measure.
+@Config(FloatingPointComputations)
 operation MeasureFxP(fp : FixedPoint) : Double {
     let measurements = MResetEachZ(fp::Register);
     let bits = ResultArrayAsBoolArray(measurements);
diff --git a/library/fixed_point/src/Operations.qs b/library/fixed_point/src/Operations.qs
index fdce658a..2c24693d 100644
--- a/library/fixed_point/src/Operations.qs
+++ b/library/fixed_point/src/Operations.qs
@@ -3,8 +3,9 @@
 
 import Types.FixedPoint;
 import Init.PrepareFxP;
-import Facts.AssertPointPositionsIdenticalFxP, Facts.AssertFormatsAreIdenticalFxP, Facts.AssertAllZeroFxP;
+import Operations.AddFxP;
 import Signed.Operations.Invert2sSI, Signed.Operations.MultiplySI, Signed.Operations.SquareSI;
+import Facts.AssertPointPositionsIdenticalFxP;
 import Std.Arrays.Zipped;
 import Std.Arithmetic.RippleCarryTTKIncByLE;
 
@@ -17,6 +18,7 @@ import Std.Arithmetic.RippleCarryTTKIncByLE;
 /// ## fp
 /// Fixed-point number to which the constant will
 /// be added.
+@Config(Unrestricted)
 operation AddConstantFxP(constant : Double, fp : FixedPoint) : Unit is Adj + Ctl {
     let n = Length(fp::Register);
     use ys = Qubit[n];
@@ -46,6 +48,7 @@ operation AddConstantFxP(constant : Double, fp : FixedPoint) : Unit is Adj + Ctl
 /// The current implementation requires the two fixed-point numbers
 /// to have the same point position counting from the least-significant
 /// bit, i.e., $n_i$ and $p_i$ must be equal.
+@Config(Unrestricted)
 operation AddFxP(fp1 : FixedPoint, fp2 : FixedPoint) : Unit is Adj + Ctl {
     AssertPointPositionsIdenticalFxP([fp1, fp2]);
 
@@ -62,6 +65,7 @@ operation AddFxP(fp1 : FixedPoint, fp2 : FixedPoint) : Unit is Adj + Ctl {
 /// # Remarks
 /// Numerical inaccuracies may occur depending on the
 /// bit-precision of the fixed-point number.
+@Config(Unrestricted)
 operation InvertFxP(fp : FixedPoint) : Unit is Adj + Ctl {
     let (_, reg) = fp!;
     Invert2sSI(reg);
@@ -79,6 +83,7 @@ operation InvertFxP(fp : FixedPoint) : Unit is Adj + Ctl {
 /// # Remarks
 /// Computes the difference by inverting `subtrahend` before and after adding
 /// it to `minuend`.  Notice that `minuend`, the first argument is updated.
+@Config(Unrestricted)
 operation SubtractFxP(minuend : FixedPoint, subtrahend : FixedPoint) : Unit is Adj + Ctl {
     within {
         InvertFxP(subtrahend);
@@ -102,13 +107,14 @@ operation SubtractFxP(minuend : FixedPoint, subtrahend : FixedPoint) : Unit is A
 /// # Remarks
 /// The current implementation requires the three fixed-point numbers
 /// to have the same point position and the same number of qubits.
+@Config(Unrestricted)
 operation MultiplyFxP(fp1 : FixedPoint, fp2 : FixedPoint, result : FixedPoint) : Unit is Adj {
 
     body (...) {
         Controlled MultiplyFxP([], (fp1, fp2, result));
     }
     controlled (controls, ...) {
-        AssertFormatsAreIdenticalFxP([fp1, fp2, result]);
+        Facts.AssertFormatsAreIdenticalFxP([fp1, fp2, result]);
         let n = Length(fp1::Register);
 
         use tmpResult = Qubit[2 * n];
@@ -133,12 +139,13 @@ operation MultiplyFxP(fp1 : FixedPoint, fp2 : FixedPoint, result : FixedPoint) :
 /// ## result
 /// Result fixed-point number,
 /// must be in state $\ket{0}$ initially.
+@Config(Unrestricted)
 operation SquareFxP(fp : FixedPoint, result : FixedPoint) : Unit is Adj {
     body (...) {
         Controlled SquareFxP([], (fp, result));
     }
     controlled (controls, ...) {
-        AssertFormatsAreIdenticalFxP([fp, result]);
+        Facts.AssertFormatsAreIdenticalFxP([fp, result]);
         let n = Length(fp::Register);
 
         use tmpResult = Qubit[2 * n];
diff --git a/library/fixed_point/src/Polynomial.qs b/library/fixed_point/src/Polynomial.qs
index 083c4848..5bc356b6 100644
--- a/library/fixed_point/src/Polynomial.qs
+++ b/library/fixed_point/src/Polynomial.qs
@@ -1,10 +1,10 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-import Facts.AssertFormatsAreIdenticalFxP, Facts.AssertAllZeroFxP;
 import Types.FixedPoint;
 import Init.PrepareFxP;
-import Operations.MultiplyFxP, Operations.SquareFxP, Operations.AddConstantFxP;
+import Facts.AssertFormatsAreIdenticalFxP;
+
 
 /// # Summary
 /// Evaluates a polynomial in a fixed-point representation.
@@ -19,12 +19,15 @@ import Operations.MultiplyFxP, Operations.SquareFxP, Operations.AddConstantFxP;
 /// ## result
 /// Output fixed-point number which will hold $P(x)$. Must be in state
 /// $\ket{0}$ initially.
+@Config(Unrestricted)
 operation EvaluatePolynomialFxP(coefficients : Double[], fpx : FixedPoint, result : FixedPoint) : Unit is Adj {
     body (...) {
         Controlled EvaluatePolynomialFxP([], (coefficients, fpx, result));
     }
     controlled (controls, ...) {
-        AssertFormatsAreIdenticalFxP([fpx, result]);
+        import Operations.AddConstantFxP;
+
+        Facts.AssertFormatsAreIdenticalFxP([fpx, result]);
         let degree = Length(coefficients) - 1;
         let p = fpx::IntegerBits;
         let n = Length(fpx::Register);
@@ -40,10 +43,11 @@ operation EvaluatePolynomialFxP(coefficients : Double[], fpx : FixedPoint, resul
                     let currentIterate = FixedPoint(p, qubits[(d - 1) * n..d * n - 1]);
                     let nextIterate = FixedPoint(p, qubits[(d - 2) * n..(d - 1) * n - 1]);
                     // multiply by x and then add current coefficient
-                    MultiplyFxP(currentIterate, fpx, nextIterate);
+                    Operations.MultiplyFxP(currentIterate, fpx, nextIterate);
                     AddConstantFxP(coefficients[d-1], nextIterate);
                 }
             } apply {
+                import Operations.MultiplyFxP;
                 let finalIterate = FixedPoint(p, qubits[0..n-1]);
                 // final multiplication into the result register
                 Controlled MultiplyFxP(controls, (finalIterate, fpx, result));
@@ -67,12 +71,15 @@ operation EvaluatePolynomialFxP(coefficients : Double[], fpx : FixedPoint, resul
 /// ## result
 /// Output fixed-point number which will hold $P(x)$. Must be in state
 /// $\ket{0}$ initially.
+@Config(Unrestricted)
 operation EvaluateEvenPolynomialFxP(coefficients : Double[], fpx : FixedPoint, result : FixedPoint) : Unit is Adj {
     body (...) {
         Controlled EvaluateEvenPolynomialFxP([], (coefficients, fpx, result));
     }
     controlled (controls, ...) {
-        AssertFormatsAreIdenticalFxP([fpx, result]);
+        import Operations.SquareFxP;
+
+        Facts.AssertFormatsAreIdenticalFxP([fpx, result]);
         let halfDegree = Length(coefficients) - 1;
         let n = Length(fpx::Register);
 
@@ -104,11 +111,13 @@ operation EvaluateEvenPolynomialFxP(coefficients : Double[], fpx : FixedPoint, r
 /// ## result
 /// Output fixed-point number which will hold P(x). Must be in state
 /// $\ket{0}$ initially.
+@Config(Unrestricted)
 operation EvaluateOddPolynomialFxP(coefficients : Double[], fpx : FixedPoint, result : FixedPoint) : Unit is Adj {
     body (...) {
         Controlled EvaluateOddPolynomialFxP([], (coefficients, fpx, result));
     }
     controlled (controls, ...) {
+        import Operations.MultiplyFxP;
         AssertFormatsAreIdenticalFxP([fpx, result]);
         let halfDegree = Length(coefficients) - 1;
         let n = Length(fpx::Register);
diff --git a/library/fixed_point/src/Reciprocal.qs b/library/fixed_point/src/Reciprocal.qs
index 4c5b4049..a80e0c0a 100644
--- a/library/fixed_point/src/Reciprocal.qs
+++ b/library/fixed_point/src/Reciprocal.qs
@@ -21,6 +21,7 @@ import Std.Math.Min;
 /// Fixed-point number to be inverted.
 /// ## result
 /// Fixed-point number that will hold the result. Must be initialized to $\ket{0.0}$.
+@Config(Unrestricted)
 operation ComputeReciprocalFxP(x : FixedPoint, result : FixedPoint) : Unit is Adj {
     body (...) {
         Controlled ComputeReciprocalFxP([], (x, result));
diff --git a/library/std/src/Std/StatePreparation.qs b/library/std/src/Std/StatePreparation.qs
index 1b67ab7d..ecf1a57a 100644
--- a/library/std/src/Std/StatePreparation.qs
+++ b/library/std/src/Std/StatePreparation.qs
@@ -1,10 +1,19 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+export
+    PreparePureStateD,
+    ApproximatelyPreparePureStateCP,
+    PrepareUniformSuperposition;
 
 import
     Std.Diagnostics.Fact,
     Std.Convert.ComplexAsComplexPolar,
+    Std.Convert.IntAsDouble,
+    Std.Arithmetic.ApplyIfGreaterLE,
+    Std.Arithmetic.ApplyIfGreaterL,
+    Std.Arithmetic.ReflectAboutInteger,
+    Std.Convert.IntAsBigInt,
     Std.Math.*,
     Std.Arrays.*;
 
@@ -385,4 +394,64 @@ function AnyOutsideToleranceD(tolerance : Double, coefficients : Double[]) : Boo
     Any(coefficient -> AbsD(coefficient) >= tolerance, coefficients)
 }
 
-export PreparePureStateD, ApproximatelyPreparePureStateCP;
+/// # Summary
+/// Prepares a uniform superposition of states that represent integers 0 through
+/// `nStates - 1` in a little-endian `qubits` register.
+///
+/// # Description
+/// Given an input state $\ket{0\cdots 0}$ this operation prepares
+/// a uniform superposition of all number states $0$ to $M-1$. In other words,
+/// $$
+/// \begin{align}
+///     \ket{0} \mapsto \frac{1}{\sqrt{M}} \sum_{j=0}^{M - 1} \ket{j}
+/// \end{align}
+/// $$
+///
+/// The operation is adjointable, but requires that `qubits` register is in a
+/// uniform superposition over the first `nStates` basis states in that case.
+///
+/// # Input
+/// ## nStates
+/// The number of states in the uniform superposition to be prepared.
+/// ## register
+/// The little-endian qubit register to store the prepared state.
+/// It is assumed to be initialized in the zero state $\ket{0\cdots 0}$.
+/// This register must be long enough to store the number $M-1$, meaning that
+/// $2^{Length(qubits)} >= M$.
+///
+/// # Example
+/// ```qsharp
+///    use qs = Qubit[4];
+///    PrepareUniformSuperposition(3, qs);
+///    DumpRegister(qs); // The state is (|0000>+|0100>+|1000>)/√3
+///    ResetAll(qs);
+/// ```
+operation PrepareUniformSuperposition(nStates : Int, qubits : Qubit[]) : Unit is Adj + Ctl {
+    Fact(nStates > 0, "Number of basis states must be positive.");
+    let nQubits = BitSizeI(nStates-1);
+    Fact(nQubits <= Length(qubits), $"Qubit register is too short to prepare {nStates} states.");
+    let relevantQubits = qubits[...nQubits - 1];
+    let nTrailingZeroes = TrailingZeroCountI(nStates);
+
+    ApplyToEachCA(H, relevantQubits);
+
+    if nTrailingZeroes != nQubits {
+        // Not a superposition of all relevant states so adjustment is needed.
+        use tgt = Qubit();
+
+        let nRelevantStates = 2^nQubits;
+        let sqrt = Sqrt(IntAsDouble(nRelevantStates) / IntAsDouble(nStates));
+        let angle = 2.0 * ArcSin(0.5 * sqrt);
+
+        ApplyIfGreaterL(Ry(2.0 * angle, _), IntAsBigInt(nStates), relevantQubits, tgt);
+
+        within {
+            ApplyToEachA(H, relevantQubits[nTrailingZeroes...]);
+        } apply {
+            ReflectAboutInteger(0, relevantQubits[nTrailingZeroes...] + [tgt]);
+            Ry(-angle, tgt);
+        }
+
+        X(tgt);
+    }
+}
diff --git a/library/std/src/Std/TableLookup.qs b/library/std/src/Std/TableLookup.qs
index 08e85093..c3a5c734 100644
--- a/library/std/src/Std/TableLookup.qs
+++ b/library/std/src/Std/TableLookup.qs
@@ -175,29 +175,34 @@ operation Unlookup(
     select : Qubit[],
     target : Qubit[]
 ) : Unit {
-    let numBits = Length(target);
-    let numAddressBits = Length(select);
+    // No measurement-based uncomputation when there is only one address
+    if Length(data) == 1 {
+        WriteMemoryContents(Head(data), target);
+    } else {
+        let numBits = Length(target);
+        let numAddressBits = Length(select);
 
-    let l = MinI(Floor(Lg(IntAsDouble(numBits))), numAddressBits - 1);
-    Fact(
-        l < numAddressBits,
-        $"l = {l} must be smaller than {numAddressBits}"
-    );
+        let l = MinI(Floor(Lg(IntAsDouble(numBits))), numAddressBits - 1);
+        Fact(
+            l < numAddressBits,
+            $"l = {l} must be smaller than {numAddressBits}"
+        );
 
-    let res = Mapped(r -> r == One, ForEach(MResetX, target));
+        let res = Mapped(r -> r == One, ForEach(MResetX, target));
 
-    let dataFixup = Chunks(2^l, Padded(-2^numAddressBits, false, Mapped(MustBeFixed(res, _), data)));
+        let dataFixup = Chunks(2^l, Padded(-2^numAddressBits, false, Mapped(MustBeFixed(res, _), data)));
 
-    let numAddressBitsFixup = numAddressBits - l;
+        let numAddressBitsFixup = numAddressBits - l;
 
-    let selectParts = Partitioned([l], select);
-    let targetFixup = target[...2^l - 1];
+        let selectParts = Partitioned([l], select);
+        let targetFixup = target[...2^l - 1];
 
-    within {
-        EncodeUnary(selectParts[0], targetFixup);
-        ApplyToEachA(H, targetFixup);
-    } apply {
-        lookup(dataFixup, selectParts[1], targetFixup);
+        within {
+            EncodeUnary(selectParts[0], targetFixup);
+            ApplyToEachA(H, targetFixup);
+        } apply {
+            lookup(dataFixup, selectParts[1], targetFixup);
+        }
     }
 }
 
diff --git a/npm/qsharp/src/browser.ts b/npm/qsharp/src/browser.ts
index 607b0116..c49647e0 100644
--- a/npm/qsharp/src/browser.ts
+++ b/npm/qsharp/src/browser.ts
@@ -191,3 +191,5 @@ export type {
 };
 
 export * as utils from "./utils.js";
+
+export type { Circuit as CircuitData } from "./shared/circuit.js";
diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts
index 453984c7..0916f111 100644
--- a/npm/qsharp/src/compiler/compiler.ts
+++ b/npm/qsharp/src/compiler/compiler.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import { type Circuit as CircuitData } from "@microsoft/quantum-viz.js/lib/circuit.js";
+import { type Circuit as CircuitData } from "../shared/circuit.js";
 import {
   IDocFile,
   IOperationInfo,
@@ -64,7 +64,11 @@ export interface ICompiler {
 
   getQir(program: ProgramConfig): Promise<string>;
 
-  getEstimates(program: ProgramConfig, params: string): Promise<string>;
+  getEstimates(
+    program: ProgramConfig,
+    expr: string,
+    params: string,
+  ): Promise<string>;
 
   getCircuit(
     program: ProgramConfig,
@@ -204,9 +208,14 @@ export class Compiler implements ICompiler {
     return this.wasm.get_qir(toWasmProgramConfig(program, "base"));
   }
 
-  async getEstimates(program: ProgramConfig, params: string): Promise<string> {
+  async getEstimates(
+    program: ProgramConfig,
+    expr: string,
+    params: string,
+  ): Promise<string> {
     return this.wasm.get_estimates(
       toWasmProgramConfig(program, "unrestricted"),
+      expr,
       params,
     );
   }
diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts
index 151d7132..b5ed11c1 100644
--- a/npm/qsharp/src/debug-service/debug-service.ts
+++ b/npm/qsharp/src/debug-service/debug-service.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import { type Circuit as CircuitData } from "@microsoft/quantum-viz.js/lib/circuit.js";
+import { type Circuit as CircuitData } from "../shared/circuit.js";
 import type {
   DebugService,
   IBreakpointSpan,
diff --git a/npm/qsharp/src/shared/README.md b/npm/qsharp/src/shared/README.md
new file mode 100644
index 00000000..79f7c19f
--- /dev/null
+++ b/npm/qsharp/src/shared/README.md
@@ -0,0 +1,1 @@
+This directory contains shared modules to be referenced from both `qsharp/src/` and `qsharp/ux/`.
diff --git a/npm/qsharp/src/shared/circuit.ts b/npm/qsharp/src/shared/circuit.ts
new file mode 100644
index 00000000..6b31e192
--- /dev/null
+++ b/npm/qsharp/src/shared/circuit.ts
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Register } from "./register.js";
+
+/**
+ * Circuit to be visualized.
+ */
+export interface Circuit {
+  /** Array of qubit resources. */
+  qubits: Qubit[];
+  operations: Operation[];
+}
+
+/**
+ * Represents a unique qubit resource bit.
+ */
+export interface Qubit {
+  /** Qubit ID. */
+  id: number;
+  /** Number of classical registers attached to quantum register. */
+  numChildren?: number;
+}
+
+/**
+ * Conditions on when to render the given operation.
+ */
+export enum ConditionalRender {
+  /** Always rendered. */
+  Always,
+  /** Render classically-controlled operation when measurement is a zero. */
+  OnZero,
+  /** Render classically-controlled operation when measurement is a one. */
+  OnOne,
+  /** Render operation as a group of its nested operations. */
+  AsGroup,
+}
+
+/**
+ * Custom data attributes (e.g. data-{attr}="{val}")
+ */
+export interface DataAttributes {
+  [attr: string]: string;
+}
+
+/**
+ * Represents an operation and the registers it acts on.
+ */
+export interface Operation {
+  /** Gate label. */
+  gate: string;
+  /** Formatted gate arguments to be displayed. */
+  displayArgs?: string;
+  /** Nested operations within this operation. */
+  children?: Operation[];
+  /** Whether gate is a measurement operation. */
+  isMeasurement: boolean;
+  /** Whether gate is a conditional operation. */
+  isConditional: boolean;
+  /** Whether gate is a controlled operation. */
+  isControlled: boolean;
+  /** Whether gate is an adjoint operation. */
+  isAdjoint: boolean;
+  /** Control registers the gate acts on. */
+  controls?: Register[];
+  /** Target registers the gate acts on. */
+  targets: Register[];
+  /** Specify conditions on when to render operation. */
+  conditionalRender?: ConditionalRender;
+  /** Custom data attributes to attach to gate element. */
+  dataAttributes?: DataAttributes;
+}
diff --git a/npm/qsharp/src/shared/register.ts b/npm/qsharp/src/shared/register.ts
new file mode 100644
index 00000000..e2fb0588
--- /dev/null
+++ b/npm/qsharp/src/shared/register.ts
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+/**
+ * Type of register.
+ */
+export enum RegisterType {
+  Qubit,
+  Classical,
+}
+
+/**
+ * Represents a register resource.
+ */
+export interface Register {
+  /** Type of register. If missing defaults to Qubit. */
+  type?: RegisterType;
+  /** Qubit register ID. */
+  qId: number;
+  /** Classical register ID (if classical register). */
+  cId?: number;
+}
+
+/**
+ * Metadata for qubit register.
+ */
+export interface RegisterMetadata {
+  /** Type of register. */
+  type: RegisterType;
+  /** y coord of register */
+  y: number;
+  /** Nested classical registers attached to quantum register. */
+  children?: RegisterMetadata[];
+}
+
+/**
+ * Mapping from qubit IDs to their register metadata.
+ */
+export interface RegisterMap {
+  [id: number]: RegisterMetadata;
+}
diff --git a/npm/qsharp/ux/circuit-vis/README.md b/npm/qsharp/ux/circuit-vis/README.md
new file mode 100644
index 00000000..0a33f95d
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/README.md
@@ -0,0 +1,1 @@
+The code in this folder was copied and adapted from `https://github.com/microsoft/quantum-viz.js`.
diff --git a/npm/qsharp/ux/circuit-vis/circuit.ts b/npm/qsharp/ux/circuit-vis/circuit.ts
new file mode 100644
index 00000000..aff04aa9
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/circuit.ts
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+export {
+  ConditionalRender,
+  type Circuit,
+  type DataAttributes,
+  type Operation,
+  type Qubit,
+} from "../../src/shared/circuit";
diff --git a/npm/qsharp/ux/circuit-vis/constants.ts b/npm/qsharp/ux/circuit-vis/constants.ts
new file mode 100644
index 00000000..fe276331
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/constants.ts
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+// SVG Namespace
+export const svgNS = "http://www.w3.org/2000/svg";
+
+// Display attributes
+/** Left padding of SVG. */
+export const leftPadding = 20;
+/** x coordinate for first operation on each register. */
+export const startX = 80;
+/** y coordinate of first register. */
+export const startY = 40;
+/** Minimum width of each gate. */
+export const minGateWidth = 40;
+/** Height of each gate. */
+export const gateHeight = 40;
+/** Padding on each side of gate. */
+export const gatePadding = 10;
+/** Padding on each side of gate label. */
+export const labelPadding = 10;
+/** Height between each qubit register. */
+export const registerHeight: number = gateHeight + gatePadding * 2;
+/** Height between classical registers. */
+export const classicalRegHeight: number = gateHeight;
+/** Group box inner padding. */
+export const groupBoxPadding = gatePadding;
+/** Padding between nested groups. */
+export const nestedGroupPadding = 2;
+/** Additional offset for control button. */
+export const controlBtnOffset = 40;
+/** Control button radius. */
+export const controlBtnRadius = 15;
+/** Default font size for gate labels. */
+export const labelFontSize = 14;
+/** Default font size for gate arguments. */
+export const argsFontSize = 12;
+/** Starting x coord for each register wire. */
+export const regLineStart = 40;
diff --git a/npm/qsharp/ux/circuit-vis/formatters/formatUtils.ts b/npm/qsharp/ux/circuit-vis/formatters/formatUtils.ts
new file mode 100644
index 00000000..c3bccd1b
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/formatters/formatUtils.ts
@@ -0,0 +1,220 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { labelFontSize, svgNS } from "../constants";
+
+// Helper functions for basic SVG components
+
+/**
+ * Create an SVG element.
+ *
+ * @param type The type of element to be created.
+ * @param attributes The attributes that define the element.
+ *
+ * @returns SVG element.
+ */
+export const createSvgElement = (
+  type: string,
+  attributes: { [attr: string]: string } = {},
+): SVGElement => {
+  const el: SVGElement = document.createElementNS(svgNS, type);
+  Object.entries(attributes).forEach(([attrName, attrVal]) =>
+    el.setAttribute(attrName, attrVal),
+  );
+  return el;
+};
+
+/**
+ * Given an array of SVG elements, group them as an SVG group using the `<g>` tag.
+ *
+ * @param svgElems   Array of SVG elements.
+ * @param attributes Key-value pairs of attributes and they values.
+ *
+ * @returns SVG element for grouped elements.
+ */
+export const group = (
+  svgElems: SVGElement[],
+  attributes: { [attr: string]: string } = {},
+): SVGElement => {
+  const el: SVGElement = createSvgElement("g", attributes);
+  svgElems.forEach((child: SVGElement) => el.appendChild(child));
+  return el;
+};
+
+/**
+ * Generate an SVG line.
+ *
+ * @param x1        x coord of starting point of line.
+ * @param y1        y coord of starting point of line.
+ * @param x2        x coord of ending point of line.
+ * @param y2        y coord fo ending point of line.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for line.
+ */
+export const line = (
+  x1: number,
+  y1: number,
+  x2: number,
+  y2: number,
+  className?: string,
+): SVGElement => {
+  const attrs: { [attr: string]: string } = {
+    x1: x1.toString(),
+    x2: x2.toString(),
+    y1: y1.toString(),
+    y2: y2.toString(),
+  };
+  if (className != null) attrs["class"] = className;
+  return createSvgElement("line", attrs);
+};
+
+/**
+ * Generate an SVG circle.
+ *
+ * @param x      x coord of circle.
+ * @param y      y coord of circle.
+ * @param radius Radius of circle.
+ *
+ * @returns SVG element for circle.
+ */
+export const circle = (
+  x: number,
+  y: number,
+  radius: number,
+  className?: string,
+): SVGElement => {
+  const attrs: { [attr: string]: string } = {
+    cx: x.toString(),
+    cy: y.toString(),
+    r: radius.toString(),
+  };
+  if (className != null) attrs["class"] = className;
+  return createSvgElement("circle", attrs);
+};
+
+/**
+ * Generate the SVG representation of a control dot used for controlled operations.
+ *
+ * @param x      x coord of circle.
+ * @param y      y coord of circle.
+ * @param radius Radius of circle.
+ *
+ * @returns SVG element for control dot.
+ */
+export const controlDot = (x: number, y: number, radius = 5): SVGElement =>
+  circle(x, y, radius, "control-dot");
+
+/**
+ * Generate the SVG representation of a unitary box that represents an arbitrary unitary operation.
+ *
+ * @param x         x coord of box.
+ * @param y         y coord of box.
+ * @param width     Width of box.
+ * @param height    Height of box.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for unitary box.
+ */
+export const box = (
+  x: number,
+  y: number,
+  width: number,
+  height: number,
+  className = "gate-unitary",
+): SVGElement =>
+  createSvgElement("rect", {
+    class: className,
+    x: x.toString(),
+    y: y.toString(),
+    width: width.toString(),
+    height: height.toString(),
+  });
+
+/**
+ * Generate the SVG text element from a given text string.
+ *
+ * @param text String to render as SVG text.
+ * @param x    Middle x coord of text.
+ * @param y    Middle y coord of text.
+ * @param fs   Font size of text.
+ *
+ * @returns SVG element for text.
+ */
+export const text = (
+  text: string,
+  x: number,
+  y: number,
+  fs: number = labelFontSize,
+): SVGElement => {
+  const el: SVGElement = createSvgElement("text", {
+    "font-size": fs.toString(),
+    x: x.toString(),
+    y: y.toString(),
+  });
+  el.textContent = text;
+  return el;
+};
+
+/**
+ * Generate the SVG representation of the arc used in the measurement box.
+ *
+ * @param x  x coord of arc.
+ * @param y  y coord of arc.
+ * @param rx x radius of arc.
+ * @param ry y radius of arc.
+ *
+ * @returns SVG element for arc.
+ */
+export const arc = (x: number, y: number, rx: number, ry: number): SVGElement =>
+  createSvgElement("path", {
+    class: "arc-measure",
+    d: `M ${x + 2 * rx} ${y} A ${rx} ${ry} 0 0 0 ${x} ${y}`,
+  });
+
+/**
+ * Generate a dashed SVG line.
+ *
+ * @param x1        x coord of starting point of line.
+ * @param y1        y coord of starting point of line.
+ * @param x2        x coord of ending point of line.
+ * @param y2        y coord fo ending point of line.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for dashed line.
+ */
+export const dashedLine = (
+  x1: number,
+  y1: number,
+  x2: number,
+  y2: number,
+  className?: string,
+): SVGElement => {
+  const el: SVGElement = line(x1, y1, x2, y2, className);
+  el.setAttribute("stroke-dasharray", "8, 8");
+  return el;
+};
+
+/**
+ * Generate the SVG representation of the dashed box used for enclosing groups of operations controlled on a classical register.
+ *
+ * @param x         x coord of box.
+ * @param y         y coord of box.
+ * @param width     Width of box.
+ * @param height    Height of box.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for dashed box.
+ */
+export const dashedBox = (
+  x: number,
+  y: number,
+  width: number,
+  height: number,
+  className?: string,
+): SVGElement => {
+  const el: SVGElement = box(x, y, width, height, className);
+  el.setAttribute("fill-opacity", "0");
+  el.setAttribute("stroke-dasharray", "8, 8");
+  return el;
+};
diff --git a/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts
new file mode 100644
index 00000000..f4be3626
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts
@@ -0,0 +1,557 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Metadata, GateType } from "../metadata";
+import {
+  minGateWidth,
+  gateHeight,
+  labelFontSize,
+  argsFontSize,
+  controlBtnRadius,
+  controlBtnOffset,
+  groupBoxPadding,
+  classicalRegHeight,
+  nestedGroupPadding,
+} from "../constants";
+import {
+  createSvgElement,
+  group,
+  line,
+  circle,
+  controlDot,
+  box,
+  text,
+  arc,
+  dashedLine,
+  dashedBox,
+} from "./formatUtils";
+
+/**
+ * Given an array of operations (in metadata format), return the SVG representation.
+ *
+ * @param opsMetadata Array of Metadata representation of operations.
+ * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
+ *
+ * @returns SVG representation of operations.
+ */
+const formatGates = (opsMetadata: Metadata[], nestedDepth = 0): SVGElement => {
+  const formattedGates: SVGElement[] = opsMetadata.map((metadata) =>
+    _formatGate(metadata, nestedDepth),
+  );
+  return group(formattedGates);
+};
+
+/**
+ * Takes in an operation's metadata and formats it into SVG.
+ *
+ * @param metadata Metadata object representation of 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;
+  switch (type) {
+    case GateType.Measure:
+      return _createGate([_measure(x, controlsY[0])], metadata, nestedDepth);
+    case GateType.Unitary:
+      return _createGate(
+        [_unitary(label, x, targetsY as number[][], width, displayArgs)],
+        metadata,
+        nestedDepth,
+      );
+    case GateType.X:
+      return _createGate([_x(metadata, nestedDepth)], metadata, nestedDepth);
+    case GateType.Swap:
+      return controlsY.length > 0
+        ? _controlledGate(metadata, nestedDepth)
+        : _createGate([_swap(metadata, nestedDepth)], metadata, nestedDepth);
+    case GateType.Cnot:
+    case GateType.ControlledUnitary:
+      return _controlledGate(metadata, nestedDepth);
+    case GateType.Group:
+      return _groupedOperations(metadata, nestedDepth);
+    case GateType.ClassicalControlled:
+      return _classicalControlled(metadata);
+    default:
+      throw new Error(`ERROR: unknown gate (${label}) of type ${type}.`);
+  }
+};
+
+/**
+ * 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.
+ *
+ * @returns SVG representation of a gate.
+ */
+const _createGate = (
+  svgElems: SVGElement[],
+  metadata: Metadata,
+  nestedDepth: number,
+): SVGElement => {
+  const { dataAttributes } = metadata || {};
+  const attributes: { [attr: string]: string } = { class: "gate" };
+  Object.entries(dataAttributes || {}).forEach(
+    ([attr, val]) => (attributes[`data-${attr}`] = val),
+  );
+
+  const zoomBtn: SVGElement | null = _zoomButton(metadata, nestedDepth);
+  if (zoomBtn != null) svgElems = svgElems.concat([zoomBtn]);
+  return group(svgElems, attributes);
+};
+
+/**
+ * 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 nestedDepth Depth of nested operation.
+ *
+ * @returns SVG element for expand/collapse button if needed, or null otherwise.
+ */
+const _zoomButton = (
+  metadata: Metadata,
+  nestedDepth: number,
+): SVGElement | null => {
+  if (metadata == undefined) return null;
+
+  const [x1, y1] = _gatePosition(metadata, nestedDepth);
+  let { dataAttributes } = metadata;
+  dataAttributes = dataAttributes || {};
+
+  const expanded = "expanded" in dataAttributes;
+
+  const x = x1 + 2;
+  const y = y1 + 2;
+  const circleBorder: SVGElement = circle(x, y, 10);
+
+  if (expanded) {
+    // Create collapse button if expanded
+    const minusSign: SVGElement = createSvgElement("path", {
+      d: `M${x - 7},${y} h14`,
+    });
+    const elements: SVGElement[] = [circleBorder, minusSign];
+    return group(elements, { class: "gate-control gate-collapse" });
+  } else if (dataAttributes["zoom-in"] == "true") {
+    // Create expand button if operation can be zoomed in
+    const plusSign: SVGElement = createSvgElement("path", {
+      d: `M${x},${y - 7} v14 M${x - 7},${y} h14`,
+    });
+    const elements: SVGElement[] = [circleBorder, plusSign];
+    return group(elements, { class: "gate-control gate-expand" });
+  }
+
+  return null;
+};
+
+/**
+ * Calculate position of gate.
+ *
+ * @param metadata Operation metadata.
+ * @param nestedDepth Depth of nested operations.
+ *
+ * @returns Coordinates of gate: [x1, y1, x2, y2].
+ */
+const _gatePosition = (
+  metadata: Metadata,
+  nestedDepth: number,
+): [number, number, number, number] => {
+  const { x, width, type, targetsY } = metadata;
+
+  const ys = targetsY?.flatMap((y) => y as number[]) || [];
+  const maxY = Math.max(...ys);
+  const minY = Math.min(...ys);
+
+  let x1: number, y1: number, x2: number, y2: number;
+
+  switch (type) {
+    case GateType.Group: {
+      const padding = groupBoxPadding - nestedDepth * nestedGroupPadding;
+
+      x1 = x - 2 * padding;
+      y1 = minY - gateHeight / 2 - padding;
+      x2 = width + 2 * padding;
+      y2 = maxY + +gateHeight / 2 + padding - (minY - gateHeight / 2 - padding);
+
+      return [x1, y1, x2, y2];
+    }
+
+    default:
+      x1 = x - width / 2;
+      y1 = minY - gateHeight / 2;
+      x2 = x + width;
+      y2 = maxY + gateHeight / 2;
+  }
+
+  return [x1, y1, x2, y2];
+};
+
+/**
+ * Creates a measurement gate at position (x, y).
+ *
+ * @param x  x coord of measurement gate.
+ * @param y  y coord of measurement gate.
+ *
+ * @returns SVG representation of measurement gate.
+ */
+const _measure = (x: number, y: number): SVGElement => {
+  x -= minGateWidth / 2;
+  const width: number = minGateWidth,
+    height = gateHeight;
+  // Draw measurement box
+  const mBox: SVGElement = box(
+    x,
+    y - height / 2,
+    width,
+    height,
+    "gate-measure",
+  );
+  const mArc: SVGElement = arc(x + 5, y + 2, width / 2 - 5, height / 2 - 8);
+  const meter: SVGElement = line(
+    x + width / 2,
+    y + 8,
+    x + width - 8,
+    y - height / 2 + 8,
+  );
+  return group([mBox, mArc, meter]);
+};
+
+/**
+ * Creates the SVG for a unitary gate on an arbitrary number of qubits.
+ *
+ * @param label            Gate label.
+ * @param x                x coord of gate.
+ * @param y                Array of y coords of registers acted upon by gate.
+ * @param width            Width of gate.
+ * @param displayArgs           Arguments passed in to gate.
+ * @param renderDashedLine If true, draw dashed lines between non-adjacent unitaries.
+ *
+ * @returns SVG representation of unitary gate.
+ */
+const _unitary = (
+  label: string,
+  x: number,
+  y: number[][],
+  width: number,
+  displayArgs?: string,
+  renderDashedLine = true,
+): SVGElement => {
+  if (y.length === 0)
+    throw new Error(
+      `Failed to render unitary gate (${label}): has no y-values`,
+    );
+
+  // Render each group as a separate unitary boxes
+  const unitaryBoxes: SVGElement[] = y.map((group: number[]) => {
+    const maxY: number = group[group.length - 1],
+      minY: number = group[0];
+    const height: number = maxY - minY + gateHeight;
+    return _unitaryBox(label, x, minY, width, height, displayArgs);
+  });
+
+  // Draw dashed line between disconnected unitaries
+  if (renderDashedLine && unitaryBoxes.length > 1) {
+    const lastBox: number[] = y[y.length - 1];
+    const firstBox: number[] = y[0];
+    const maxY: number = lastBox[lastBox.length - 1],
+      minY: number = firstBox[0];
+    const vertLine: SVGElement = dashedLine(x, minY, x, maxY);
+    return group([vertLine, ...unitaryBoxes]);
+  }
+
+  return group(unitaryBoxes);
+};
+
+/**
+ * Generates SVG representation of the boxed unitary gate symbol.
+ *
+ * @param label  Label for unitary operation.
+ * @param x      x coord of gate.
+ * @param y      y coord of gate.
+ * @param width  Width of gate.
+ * @param height Height of gate.
+ * @param displayArgs Arguments passed in to gate.
+ *
+ * @returns SVG representation of unitary box.
+ */
+const _unitaryBox = (
+  label: string,
+  x: number,
+  y: number,
+  width: number,
+  height: number = gateHeight,
+  displayArgs?: string,
+): SVGElement => {
+  y -= gateHeight / 2;
+  const uBox: SVGElement = box(x - width / 2, y, width, height);
+  const labelY = y + height / 2 - (displayArgs == null ? 0 : 7);
+  const labelText: SVGElement = text(label, x, labelY);
+  const elems = [uBox, labelText];
+  if (displayArgs != null) {
+    const argStrY = y + height / 2 + 8;
+    const argText: SVGElement = text(displayArgs, x, argStrY, argsFontSize);
+    elems.push(argText);
+  }
+  return group(elems);
+};
+
+/**
+ * Creates the SVG for a SWAP gate on y coords given by targetsY.
+ *
+ * @param x          Centre x coord of SWAP gate.
+ * @param targetsY   y coords of target registers.
+ *
+ * @returns SVG representation of SWAP gate.
+ */
+const _swap = (metadata: Metadata, nestedDepth: number): SVGElement => {
+  const { x, targetsY } = metadata;
+
+  // Get SVGs of crosses
+  const [x1, y1, x2, y2] = _gatePosition(metadata, nestedDepth);
+  const ys = targetsY?.flatMap((y) => y as number[]) || [];
+
+  const bg: SVGElement = box(x1, y1, x2, y2, "gate-swap");
+  const crosses: SVGElement[] = ys.map((y) => _cross(x, y));
+  const vertLine: SVGElement = line(x, ys[0], x, ys[1]);
+  return group([bg, ...crosses, vertLine]);
+};
+/**
+ * Creates the SVG for an X gate
+ *
+ * @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 ys = targetsY.flatMap((y) => y as number[]);
+  return _oplus(x, ys[0]);
+};
+/**
+ * Generates cross for display in SWAP gate.
+ *
+ * @param x x coord of gate.
+ * @param y y coord of gate.
+ *
+ * @returns SVG representation for cross.
+ */
+const _cross = (x: number, y: number): SVGElement => {
+  const radius = 8;
+  const line1: SVGElement = line(
+    x - radius,
+    y - radius,
+    x + radius,
+    y + radius,
+  );
+  const line2: SVGElement = line(
+    x - radius,
+    y + radius,
+    x + radius,
+    y - radius,
+  );
+  return group([line1, line2]);
+};
+
+/**
+ * Produces the SVG representation of a controlled gate on multiple qubits.
+ *
+ * @param metadata Metadata of controlled gate.
+ *
+ * @returns SVG representation of controlled gate.
+ */
+const _controlledGate = (
+  metadata: Metadata,
+  nestedDepth: number,
+): SVGElement => {
+  const targetGateSvgs: SVGElement[] = [];
+  const { type, x, controlsY, label, displayArgs, width } = metadata;
+  let { targetsY } = metadata;
+
+  // Get SVG for target gates
+  switch (type) {
+    case GateType.Cnot:
+      (targetsY as number[]).forEach((y) => targetGateSvgs.push(_oplus(x, y)));
+      break;
+    case GateType.Swap:
+      (targetsY as number[]).forEach((y) => targetGateSvgs.push(_cross(x, y)));
+      break;
+    case GateType.ControlledUnitary:
+      {
+        const groupedTargetsY: number[][] = targetsY as number[][];
+        targetGateSvgs.push(
+          _unitary(label, x, groupedTargetsY, width, displayArgs, false),
+        );
+        targetsY = targetsY.flat();
+      }
+      break;
+    default:
+      throw new Error(`ERROR: Unrecognized gate: ${label} of type ${type}`);
+  }
+  // Get SVGs for control dots
+  const controlledDotsSvg: SVGElement[] = controlsY.map((y) =>
+    controlDot(x, y),
+  );
+  // Create control lines
+  const maxY: number = Math.max(...controlsY, ...(targetsY as number[]));
+  const minY: number = Math.min(...controlsY, ...(targetsY as number[]));
+  const vertLine: SVGElement = line(x, minY, x, maxY);
+  const svg: SVGElement = _createGate(
+    [vertLine, ...controlledDotsSvg, ...targetGateSvgs],
+    metadata,
+    nestedDepth,
+  );
+  return svg;
+};
+
+/**
+ * Generates $\oplus$ symbol for display in CNOT gate.
+ *
+ * @param x x coordinate of gate.
+ * @param y y coordinate of gate.
+ * @param r radius of circle.
+ *
+ * @returns SVG representation of $\oplus$ symbol.
+ */
+const _oplus = (x: number, y: number, r = 15): SVGElement => {
+  const circleBorder: SVGElement = circle(x, y, r);
+  const vertLine: SVGElement = line(x, y - r, x, y + r);
+  const horLine: SVGElement = line(x - r, y, x + r, y);
+  return group([circleBorder, vertLine, horLine], { class: "oplus" });
+};
+
+/**
+ * Generates the SVG for a group of nested operations.
+ *
+ * @param metadata Metadata representation of gate.
+ * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
+ *
+ * @returns SVG representation of gate.
+ */
+const _groupedOperations = (
+  metadata: Metadata,
+  nestedDepth: number,
+): SVGElement => {
+  const { children } = metadata;
+  const [x1, y1, x2, y2] = _gatePosition(metadata, 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);
+};
+
+/**
+ * Generates the SVG for a classically controlled group of operations.
+ *
+ * @param metadata Metadata representation of gate.
+ * @param padding  Padding within dashed box.
+ *
+ * @returns SVG representation of gate.
+ */
+const _classicalControlled = (
+  metadata: Metadata,
+  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 controlY = controlsY[0];
+
+  const elems: SVGElement[] = [];
+
+  if (children != null) {
+    if (children.length !== 2)
+      throw new Error(
+        `Invalid number of children found for classically-controlled gate: ${children.length}`,
+      );
+
+    // Get SVG for gates controlled on 0
+    const childrenZero: SVGElement = formatGates(children[0]);
+    childrenZero.setAttribute("class", "gates-zero");
+    elems.push(childrenZero);
+
+    // Get SVG for gates controlled on 1
+    const childrenOne: SVGElement = formatGates(children[1]);
+    childrenOne.setAttribute("class", "gates-one");
+    elems.push(childrenOne);
+  }
+
+  // Draw control button and attached dashed line to dashed box
+  const controlCircleX: number = x + controlBtnRadius;
+  const controlCircle: SVGElement = _controlCircle(controlCircleX, controlY);
+  const lineY1: number = controlY + controlBtnRadius,
+    lineY2: number = controlY + classicalRegHeight / 2;
+  const vertLine: SVGElement = dashedLine(
+    controlCircleX,
+    lineY1,
+    controlCircleX,
+    lineY2,
+    "classical-line",
+  );
+  x += controlBtnOffset;
+  const horLine: SVGElement = dashedLine(
+    controlCircleX,
+    lineY2,
+    x,
+    lineY2,
+    "classical-line",
+  );
+
+  width = width - controlBtnOffset + (padding - groupBoxPadding) * 2;
+  x += groupBoxPadding - padding;
+  const y: number = targetsY[0] - gateHeight / 2 - padding;
+  const height: number = targetsY[1] - targetsY[0] + gateHeight + padding * 2;
+
+  // Draw dashed box around children gates
+  const box: SVGElement = dashedBox(x, y, width, height, "classical-container");
+
+  elems.push(...[horLine, vertLine, controlCircle, box]);
+
+  // Display controlled operation in initial "unknown" state
+  const attributes: { [attr: string]: string } = {
+    class: `classically-controlled-group classically-controlled-unknown`,
+  };
+  if (dataAttributes != null)
+    Object.entries(dataAttributes).forEach(
+      ([attr, val]) => (attributes[`data-${attr}`] = val),
+    );
+
+  return group(elems, attributes);
+};
+
+/**
+ * Generates the SVG representation of the control circle on a classical register with interactivity support
+ * for toggling between bit values (unknown, 1, and 0).
+ *
+ * @param x   x coord.
+ * @param y   y coord.
+ * @param r   Radius of circle.
+ *
+ * @returns SVG representation of control circle.
+ */
+const _controlCircle = (
+  x: number,
+  y: number,
+  r: number = controlBtnRadius,
+): SVGElement =>
+  group([circle(x, y, r), text("?", x, y, labelFontSize)], {
+    class: "classically-controlled-btn",
+  });
+
+export {
+  formatGates,
+  _formatGate,
+  _createGate,
+  _zoomButton,
+  _measure,
+  _unitary,
+  _swap,
+  _controlledGate,
+  _groupedOperations,
+  _classicalControlled,
+};
diff --git a/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts
new file mode 100644
index 00000000..7464de4c
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Qubit } from "../circuit";
+import { RegisterType, RegisterMap, RegisterMetadata } from "../register";
+import {
+  leftPadding,
+  startY,
+  registerHeight,
+  classicalRegHeight,
+} from "../constants";
+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).
+ *
+ * @param qubits List of declared qubits.
+ *
+ * @returns returns the SVG string of formatted qubit wires, a mapping from registers
+ *          to y coord and total SVG height.
+ */
+const formatInputs = (
+  qubits: Qubit[],
+): { qubitWires: SVGElement; registers: RegisterMap; svgHeight: number } => {
+  const qubitWires: SVGElement[] = [];
+  const registers: RegisterMap = {};
+
+  let currY: number = startY;
+  qubits.forEach(({ id, numChildren }) => {
+    // Add qubit wire to list of qubit wires
+    qubitWires.push(_qubitInput(currY));
+
+    // Create qubit register
+    registers[id] = { type: RegisterType.Qubit, y: currY };
+
+    // If there are no attached classical registers, increment y by fixed register height
+    if (numChildren == null || numChildren === 0) {
+      currY += registerHeight;
+      return;
+    }
+
+    // Increment current height by classical register height for attached classical registers
+    currY += classicalRegHeight;
+
+    // Add classical wires
+    registers[id].children = Array.from(Array(numChildren), () => {
+      const clsReg: RegisterMetadata = {
+        type: RegisterType.Classical,
+        y: currY,
+      };
+      currY += classicalRegHeight;
+      return clsReg;
+    });
+  });
+
+  return {
+    qubitWires: group(qubitWires),
+    registers,
+    svgHeight: currY,
+  };
+};
+
+/**
+ * Generate the SVG text component for the input qubit register.
+ *
+ * @param y y coord of input wire to render in SVG.
+ *
+ * @returns SVG text component for the input register.
+ */
+const _qubitInput = (y: number): SVGElement => {
+  const el: SVGElement = text("|0⟩", leftPadding, y, 16);
+  el.setAttribute("text-anchor", "start");
+  el.setAttribute("dominant-baseline", "middle");
+  return el;
+};
+
+export { formatInputs, _qubitInput };
diff --git a/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts b/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts
new file mode 100644
index 00000000..aa858e92
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/formatters/registerFormatter.ts
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { RegisterMap } from "../register";
+import { regLineStart } from "../constants";
+import { Metadata, GateType } from "../metadata";
+import { group, line, text } 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 endX         End x coord.
+ *
+ * @returns SVG representation of register wires.
+ */
+const formatRegisters = (
+  registers: RegisterMap,
+  measureGates: Metadata[],
+  endX: number,
+): SVGElement => {
+  const formattedRegs: SVGElement[] = [];
+  // Render qubit wires
+  for (const qId in registers) {
+    formattedRegs.push(_qubitRegister(Number(qId), endX, registers[qId].y));
+  }
+  // Render classical wires
+  measureGates.forEach(({ type, x, targetsY, controlsY }) => {
+    if (type !== GateType.Measure) return;
+    const gateY: number = controlsY[0];
+    (targetsY as number[]).forEach((y) => {
+      formattedRegs.push(_classicalRegister(x, gateY, endX, y));
+    });
+  });
+  return group(formattedRegs);
+};
+
+/**
+ * Generates the SVG representation of a classical register.
+ *
+ * @param startX Start x coord.
+ * @param gateY  y coord of measurement gate.
+ * @param endX   End x coord.
+ * @param wireY  y coord of wire.
+ *
+ * @returns SVG representation of the given classical register.
+ */
+const _classicalRegister = (
+  startX: number,
+  gateY: number,
+  endX: number,
+  wireY: number,
+): SVGElement => {
+  const wirePadding = 1;
+  // Draw vertical lines
+  const vLine1: SVGElement = line(
+    startX + wirePadding,
+    gateY,
+    startX + wirePadding,
+    wireY - wirePadding,
+    "register-classical",
+  );
+  const vLine2: SVGElement = line(
+    startX - wirePadding,
+    gateY,
+    startX - wirePadding,
+    wireY + wirePadding,
+    "register-classical",
+  );
+
+  // Draw horizontal lines
+  const hLine1: SVGElement = line(
+    startX + wirePadding,
+    wireY - wirePadding,
+    endX,
+    wireY - wirePadding,
+    "register-classical",
+  );
+  const hLine2: SVGElement = line(
+    startX - wirePadding,
+    wireY + wirePadding,
+    endX,
+    wireY + wirePadding,
+    "register-classical",
+  );
+
+  return group([vLine1, vLine2, hLine1, hLine2]);
+};
+
+/**
+ * Generates the SVG representation of a qubit register.
+ *
+ * @param qId         Qubit register index.
+ * @param endX        End x coord.
+ * @param y           y coord of wire.
+ * @param labelOffset y offset for wire label.
+ *
+ * @returns SVG representation of the given qubit register.
+ */
+const _qubitRegister = (
+  qId: number,
+  endX: number,
+  y: number,
+  labelOffset = 16,
+): SVGElement => {
+  const wire: SVGElement = line(regLineStart, y, endX, y);
+
+  const label: SVGElement = text(`q${qId}`, regLineStart, y - labelOffset);
+  label.setAttribute("dominant-baseline", "hanging");
+  label.setAttribute("text-anchor", "start");
+  label.setAttribute("font-size", "75%");
+
+  return group([wire, label]);
+};
+
+export { formatRegisters, _classicalRegister, _qubitRegister };
diff --git a/npm/qsharp/ux/circuit-vis/index.ts b/npm/qsharp/ux/circuit-vis/index.ts
new file mode 100644
index 00000000..5e37c199
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/index.ts
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Sqore } from "./sqore";
+import { Circuit } from "./circuit";
+import { StyleConfig } from "./styles";
+
+/**
+ * Render `circuit` into `container` at the specified layer depth.
+ *
+ * @param circuit Circuit to be visualized.
+ * @param container HTML element for rendering visualization into.
+ * @param style Custom visualization style.
+ * @param renderDepth Initial layer depth at which to render gates.
+ */
+export const draw = (
+  circuit: Circuit,
+  container: HTMLElement,
+  style: StyleConfig | string = {},
+  renderDepth = 0,
+): void => {
+  const sqore = new Sqore(circuit, style);
+  sqore.draw(container, renderDepth);
+};
+
+export { STYLES } from "./styles";
+
+// Export types
+export type { StyleConfig } from "./styles";
+export type { Circuit, Qubit, Operation } from "./circuit";
diff --git a/npm/qsharp/ux/circuit-vis/metadata.ts b/npm/qsharp/ux/circuit-vis/metadata.ts
new file mode 100644
index 00000000..4c5c2652
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/metadata.ts
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { DataAttributes } from "./circuit";
+
+/**
+ * Enum for the various gate operations handled.
+ */
+export enum GateType {
+  /** Measurement gate. */
+  Measure,
+  /** CNOT gate. */
+  Cnot,
+  /** SWAP gate. */
+  Swap,
+  /** X gate. */
+  X,
+  /** Single/multi qubit unitary gate. */
+  Unitary,
+  /** Single/multi controlled unitary gate. */
+  ControlledUnitary,
+  /** Nested group of classically-controlled gates. */
+  ClassicalControlled,
+  /** Group of nested gates */
+  Group,
+  /** Invalid gate. */
+  Invalid,
+}
+
+/**
+ * Metadata used to store information pertaining to a given
+ * operation for rendering its corresponding SVG.
+ */
+export interface Metadata {
+  /** Gate type. */
+  type: GateType;
+  /** Centre x coord for gate position. */
+  x: number;
+  /** Array of y coords of control registers. */
+  controlsY: number[];
+  /** Array of y coords of target registers.
+   *  For `GateType.Unitary` or `GateType.ControlledUnitary`, this is an array of groups of
+   *  y coords, where each group represents a unitary box to be rendered separately.
+   */
+  targetsY: (number | number[])[];
+  /** Gate label. */
+  label: string;
+  /** Gate arguments as string. */
+  displayArgs?: string;
+  /** Gate width. */
+  width: number;
+  /** Children operations as part of group. */
+  children?: (Metadata | Metadata[])[];
+  /** Custom data attributes to attach to gate element. */
+  dataAttributes?: DataAttributes;
+}
diff --git a/npm/qsharp/ux/circuit-vis/process.ts b/npm/qsharp/ux/circuit-vis/process.ts
new file mode 100644
index 00000000..f18d7be1
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/process.ts
@@ -0,0 +1,536 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import {
+  minGateWidth,
+  startX,
+  gatePadding,
+  controlBtnOffset,
+  groupBoxPadding,
+} from "./constants";
+import { Operation, ConditionalRender } from "./circuit";
+import { Metadata, GateType } from "./metadata";
+import { Register, RegisterMap, RegisterType } from "./register";
+import { getGateWidth } from "./utils";
+
+/**
+ * Takes in a list of operations and maps them to `metadata` objects which
+ * contains information for formatting the corresponding SVG.
+ *
+ * @param operations Array of operations.
+ * @param registers  Mapping from qubit IDs to register metadata.
+ *
+ * @returns An object containing `metadataList` (Array of Metadata objects) and
+ *          `svgWidth` which is the width of the entire SVG.
+ */
+const processOperations = (
+  operations: Operation[],
+  registers: RegisterMap,
+): { metadataList: Metadata[]; svgWidth: number } => {
+  if (operations.length === 0) return { metadataList: [], svgWidth: startX };
+
+  // Group operations based on registers
+  const groupedOps: number[][] = _groupOperations(operations, registers);
+
+  // Align operations on multiple registers
+  const alignedOps: (number | null)[][] = _alignOps(groupedOps);
+
+  // Maintain widths of each column to account for variable-sized gates
+  const numColumns: number = Math.max(
+    0,
+    ...alignedOps.map((ops) => ops.length),
+  );
+  const columnsWidths: number[] = new Array(numColumns).fill(minGateWidth);
+
+  // Get classical registers and their starting column index
+  const classicalRegs: [number, Register][] = _getClassicalRegStart(
+    operations,
+    alignedOps,
+  );
+
+  // Keep track of which ops are already seen to avoid duplicate rendering
+  const visited: { [opIdx: number]: boolean } = {};
+
+  // Map operation index to gate metadata for formatting later
+  const opsMetadata: Metadata[][] = alignedOps.map((regOps) =>
+    regOps.map((opIdx, col) => {
+      let op: Operation | null = null;
+
+      // eslint-disable-next-line no-prototype-builtins
+      if (opIdx != null && !visited.hasOwnProperty(opIdx)) {
+        op = operations[opIdx];
+        visited[opIdx] = true;
+      }
+
+      const metadata: Metadata = _opToMetadata(op, registers);
+
+      if (
+        op != null &&
+        [GateType.Unitary, 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
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          .filter(([regCol, _]) => regCol <= col)
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          .map(([_, reg]) => {
+            if (reg.cId == null)
+              throw new Error("Could not find cId for classical register.");
+            const { children } = registers[reg.qId];
+            if (children == null)
+              throw new Error(
+                `Failed to find classical registers for qubit ID ${reg.qId}.`,
+              );
+            return children[reg.cId].y;
+          });
+
+        metadata.targetsY = _splitTargetsY(
+          op.targets,
+          classicalRegY,
+          registers,
+        );
+      }
+
+      // Expand column size, if needed
+      if (metadata.width > columnsWidths[col]) {
+        columnsWidths[col] = metadata.width;
+      }
+
+      return metadata;
+    }),
+  );
+
+  // Fill in x coord of each gate
+  const endX: number = _fillMetadataX(opsMetadata, columnsWidths);
+
+  // Flatten operations and filter out invalid gates
+  const metadataList: Metadata[] = opsMetadata
+    .flat()
+    .filter(({ type }) => type != GateType.Invalid);
+
+  return { metadataList, svgWidth: endX };
+};
+
+/**
+ * Group gates provided by operations into their respective registers.
+ *
+ * @param operations Array of operations.
+ * @param numRegs    Total number of registers.
+ *
+ * @returns 2D array of indices where `groupedOps[i][j]` is the index of the operations
+ *          at register `i` and column `j` (not yet aligned/padded).
+ */
+const _groupOperations = (
+  operations: Operation[],
+  registers: RegisterMap,
+): number[][] => {
+  // NOTE: We get the max ID instead of just number of keys because there can be a qubit ID that
+  // isn't acted upon and thus does not show up as a key in registers.
+  const numRegs: number =
+    Math.max(-1, ...Object.keys(registers).map(Number)) + 1;
+  const groupedOps: number[][] = Array.from(Array(numRegs), () => new Array(0));
+  operations.forEach(({ targets, controls }, instrIdx) => {
+    const ctrls: Register[] = controls || [];
+    const qRegs: Register[] = [...ctrls, ...targets].filter(
+      ({ type }) => (type || RegisterType.Qubit) === RegisterType.Qubit,
+    );
+    const qRegIdxList: number[] = qRegs.map(({ qId }) => qId);
+    const clsControls: Register[] = ctrls.filter(
+      ({ type }) => (type || RegisterType.Qubit) === RegisterType.Classical,
+    );
+    const isClassicallyControlled: boolean = clsControls.length > 0;
+    if (!isClassicallyControlled && qRegs.length === 0) return;
+    // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad
+    // the contiguous range of registers that it covers.
+    const minRegIdx: number = isClassicallyControlled
+      ? 0
+      : Math.min(...qRegIdxList);
+    const maxRegIdx: number = isClassicallyControlled
+      ? numRegs - 1
+      : Math.max(...qRegIdxList);
+    // Add operation also to registers that are in-between target registers
+    // so that other gates won't render in the middle.
+    for (let i = minRegIdx; i <= maxRegIdx; i++) {
+      groupedOps[i].push(instrIdx);
+    }
+  });
+  return groupedOps;
+};
+
+/**
+ * Aligns operations by padding registers with `null`s to make sure that multiqubit
+ * gates are in the same column.
+ * e.g. ---[x]---[x]--
+ *      ----------|---
+ *
+ * @param ops 2D array of operations. Each row represents a register
+ *            and the operations acting on it (in-order).
+ *
+ * @returns 2D array of aligned operations padded with `null`s.
+ */
+const _alignOps = (ops: number[][]): (number | null)[][] => {
+  let maxNumOps: number = Math.max(0, ...ops.map((regOps) => regOps.length));
+  let col = 0;
+  // Deep copy ops to be returned as paddedOps
+  const paddedOps: (number | null)[][] = JSON.parse(JSON.stringify(ops));
+  while (col < maxNumOps) {
+    for (let regIdx = 0; regIdx < paddedOps.length; regIdx++) {
+      const reg: (number | null)[] = paddedOps[regIdx];
+      if (reg.length <= col) continue;
+
+      // Should never be null (nulls are only padded to previous columns)
+      const opIdx: number | null = reg[col];
+
+      // Get position of gate
+      const targetsPos: number[] = paddedOps.map((regOps) =>
+        regOps.indexOf(opIdx),
+      );
+      const gatePos: number = Math.max(-1, ...targetsPos);
+
+      // If current column is not desired gate position, pad with null
+      if (col < gatePos) {
+        paddedOps[regIdx].splice(col, 0, null);
+        maxNumOps = Math.max(maxNumOps, paddedOps[regIdx].length);
+      }
+    }
+    col++;
+  }
+  return paddedOps;
+};
+
+/**
+ * Retrieves the starting index of each classical register.
+ *
+ * @param ops     Array of operations.
+ * @param idxList 2D array of aligned operation indices.
+ *
+ * @returns Array of classical register and their starting column indices in the form [[column, register]].
+ */
+const _getClassicalRegStart = (
+  ops: Operation[],
+  idxList: (number | null)[][],
+): [number, Register][] => {
+  const clsRegs: [number, Register][] = [];
+  idxList.forEach((reg) => {
+    for (let col = 0; col < reg.length; col++) {
+      const opIdx: number | null = reg[col];
+      if (opIdx != null && ops[opIdx].isMeasurement) {
+        const targetClsRegs: Register[] = ops[opIdx].targets.filter(
+          (reg) => reg.type === RegisterType.Classical,
+        );
+        targetClsRegs.forEach((reg) => clsRegs.push([col, reg]));
+      }
+    }
+  });
+  return clsRegs;
+};
+
+/**
+ * Maps operation to metadata (e.g. gate type, position, dimensions, text)
+ * required to render the image.
+ *
+ * @param op        Operation to be mapped into metadata format.
+ * @param registers Array of registers.
+ *
+ * @returns Metadata representation of given operation.
+ */
+const _opToMetadata = (
+  op: Operation | null,
+  registers: RegisterMap,
+): Metadata => {
+  const metadata: Metadata = {
+    type: GateType.Invalid,
+    x: 0,
+    controlsY: [],
+    targetsY: [],
+    label: "",
+    width: -1,
+  };
+
+  if (op == null) return metadata;
+
+  const {
+    gate,
+    dataAttributes,
+    displayArgs,
+    isMeasurement,
+    isConditional,
+    isControlled,
+    isAdjoint,
+    controls,
+    targets,
+    children,
+    conditionalRender,
+  } = op;
+
+  // Set y coords
+  metadata.controlsY = controls?.map((reg) => _getRegY(reg, registers)) || [];
+  metadata.targetsY = targets.map((reg) => _getRegY(reg, registers));
+
+  if (isConditional) {
+    // Classically-controlled operations
+    if (children == null || children.length == 0)
+      throw new Error(
+        "No children operations found for classically-controlled operation.",
+      );
+
+    // Gates to display when classical bit is 0.
+    const onZeroOps: Operation[] = children.filter(
+      (op) => op.conditionalRender !== ConditionalRender.OnOne,
+    );
+    let childrenInstrs = processOperations(onZeroOps, registers);
+    const zeroGates: Metadata[] = childrenInstrs.metadataList;
+    const zeroChildWidth: number = childrenInstrs.svgWidth;
+
+    // Gates to display when classical bit is 1.
+    const onOneOps: Operation[] = children.filter(
+      (op) => op.conditionalRender !== ConditionalRender.OnZero,
+    );
+    childrenInstrs = processOperations(onOneOps, registers);
+    const oneGates: Metadata[] = childrenInstrs.metadataList;
+    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];
+    // Add additional width from control button and inner box padding for dashed box
+    metadata.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)];
+  } else if (
+    conditionalRender == ConditionalRender.AsGroup &&
+    (children?.length || 0) > 0
+  ) {
+    const childrenInstrs = processOperations(
+      children as Operation[],
+      registers,
+    );
+    metadata.type = GateType.Group;
+    metadata.children = childrenInstrs.metadataList;
+    // _zoomButton function in gateFormatter.ts relies on
+    // 'expanded' attribute to render zoom button
+    metadata.dataAttributes = { expanded: "true" };
+    // Subtract startX (left-side) and add inner box padding (minus nested gate padding) for dashed box
+    metadata.width =
+      childrenInstrs.svgWidth - startX + (groupBoxPadding - gatePadding) * 2;
+  } else if (isMeasurement) {
+    metadata.type = GateType.Measure;
+  } else if (gate === "SWAP") {
+    metadata.type = GateType.Swap;
+  } else if (isControlled) {
+    metadata.type = gate === "X" ? GateType.Cnot : GateType.ControlledUnitary;
+    metadata.label = gate;
+  } else if (gate === "X") {
+    metadata.type = GateType.X;
+    metadata.label = gate;
+  } else {
+    // Any other gate treated as a simple unitary gate
+    metadata.type = GateType.Unitary;
+    metadata.label = gate;
+  }
+
+  // If adjoint, add ' to the end of gate label
+  if (isAdjoint && metadata.label.length > 0) metadata.label += "'";
+
+  // If gate has extra arguments, display them
+  if (displayArgs != null) metadata.displayArgs = displayArgs;
+
+  // Set gate width
+  metadata.width = getGateWidth(metadata);
+
+  // Extend existing data attributes with user-provided data attributes
+  if (dataAttributes != null)
+    metadata.dataAttributes = { ...metadata.dataAttributes, ...dataAttributes };
+
+  return metadata;
+};
+
+/**
+ * Compute the y coord of a given register.
+ *
+ * @param reg       Register to compute y coord of.
+ * @param registers Map of qubit IDs to RegisterMetadata.
+ *
+ * @returns The y coord of give register.
+ */
+const _getRegY = (reg: Register, registers: RegisterMap): number => {
+  const { type, qId, cId } = reg;
+  if (!Object.prototype.hasOwnProperty.call(registers, qId))
+    throw new Error(`ERROR: Qubit register with ID ${qId} not found.`);
+  const { y, children } = registers[qId];
+  switch (type) {
+    case undefined:
+    case RegisterType.Qubit:
+      return y;
+    case RegisterType.Classical:
+      if (children == null)
+        throw new Error(
+          `ERROR: No classical registers found for qubit ID ${qId}.`,
+        );
+      if (cId == null)
+        throw new Error(
+          `ERROR: No ID defined for classical register associated with qubit ID ${qId}.`,
+        );
+      if (children.length <= cId)
+        throw new Error(
+          `ERROR: Classical register ID ${cId} invalid for qubit ID ${qId} with ${children.length} classical register(s).`,
+        );
+      return children[cId].y;
+    default:
+      throw new Error(`ERROR: Unknown register type ${type}.`);
+  }
+};
+
+/**
+ * Splits `targets` if non-adjacent or intersected by classical registers.
+ *
+ * @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.
+ *
+ * @returns Groups of target qubit y coords.
+ */
+const _splitTargetsY = (
+  targets: Register[],
+  classicalRegY: number[],
+  registers: RegisterMap,
+): number[][] => {
+  if (targets.length === 0) return [];
+
+  // Get qIds sorted by ascending y value
+  const orderedQIds: number[] = Object.keys(registers).map(Number);
+  orderedQIds.sort((a, b) => registers[a].y - registers[b].y);
+  const qIdPosition: { [qId: number]: number } = {};
+  orderedQIds.forEach((qId, i) => (qIdPosition[qId] = i));
+
+  // Sort targets and classicalRegY by ascending y value
+  targets = targets.slice();
+  targets.sort((a, b) => {
+    const posDiff: number = qIdPosition[a.qId] - qIdPosition[b.qId];
+    if (posDiff === 0 && a.cId != null && b.cId != null) return a.cId - b.cId;
+    else return posDiff;
+  });
+  classicalRegY = classicalRegY.slice();
+  classicalRegY.sort((a, b) => a - b);
+
+  let prevPos = 0;
+  let prevY = 0;
+
+  return targets.reduce((groups: number[][], target: Register) => {
+    const y = _getRegY(target, registers);
+    const pos = qIdPosition[target.qId];
+
+    // Split into new group if one of the following holds:
+    //      1. First target register
+    //      2. Non-adjacent qubit registers
+    //      3. There is a classical register between current and previous register
+    if (
+      groups.length === 0 ||
+      pos > prevPos + 1 ||
+      (classicalRegY[0] > prevY && classicalRegY[0] < y)
+    )
+      groups.push([y]);
+    else groups[groups.length - 1].push(y);
+
+    prevPos = pos;
+    prevY = y;
+
+    // Remove classical registers that are higher than current y
+    while (classicalRegY.length > 0 && classicalRegY[0] <= y)
+      classicalRegY.shift();
+
+    return groups;
+  }, []);
+};
+
+/**
+ * Updates the x coord of each metadata in the given 2D array of metadata and returns rightmost x coord.
+ *
+ * @param opsMetadata  2D array of metadata.
+ * @param columnWidths Array of column widths.
+ *
+ * @returns Rightmost x coord.
+ */
+const _fillMetadataX = (
+  opsMetadata: Metadata[][],
+  columnWidths: number[],
+): number => {
+  let currX: number = startX;
+
+  const colStartX: number[] = columnWidths.map((width) => {
+    const x: number = currX;
+    currX += width + gatePadding * 2;
+    return x;
+  });
+
+  const endX: number = currX;
+
+  opsMetadata.forEach((regOps) =>
+    regOps.forEach((metadata, col) => {
+      const x = colStartX[col];
+      switch (metadata.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)
+              offset += controlBtnOffset;
+
+            // Offset each x coord in children gates
+            _offsetChildrenX(metadata.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;
+          }
+          break;
+
+        default:
+          metadata.x = x + columnWidths[col] / 2;
+          break;
+      }
+    }),
+  );
+
+  return endX;
+};
+
+/**
+ * Offset x coords of nested children operations.
+ *
+ * @param children 2D array of children metadata.
+ * @param offset   x coord offset.
+ */
+const _offsetChildrenX = (
+  children: (Metadata | Metadata[])[] | undefined,
+  offset: number,
+): void => {
+  if (children == null) return;
+  children.flat().forEach((child) => {
+    child.x += offset;
+    _offsetChildrenX(child.children, offset);
+  });
+};
+
+export {
+  processOperations,
+  _groupOperations,
+  _alignOps,
+  _getClassicalRegStart,
+  _opToMetadata,
+  _getRegY,
+  _splitTargetsY,
+  _fillMetadataX,
+  _offsetChildrenX,
+};
diff --git a/npm/qsharp/ux/circuit-vis/register.ts b/npm/qsharp/ux/circuit-vis/register.ts
new file mode 100644
index 00000000..ffcd9cd2
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/register.ts
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+export {
+  RegisterType,
+  type Register,
+  type RegisterMap,
+  type RegisterMetadata,
+} from "../../src/shared/register";
diff --git a/npm/qsharp/ux/circuit-vis/sqore.ts b/npm/qsharp/ux/circuit-vis/sqore.ts
new file mode 100644
index 00000000..2c326326
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/sqore.ts
@@ -0,0 +1,376 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { formatInputs } from "./formatters/inputFormatter";
+import { formatGates } from "./formatters/gateFormatter";
+import { formatRegisters } from "./formatters/registerFormatter";
+import { processOperations } from "./process";
+import { ConditionalRender, Circuit, Operation } from "./circuit";
+import { Metadata, GateType } from "./metadata";
+import { StyleConfig, style, STYLES } from "./styles";
+import { createUUID } from "./utils";
+import { svgNS } from "./constants";
+
+/**
+ * Contains metadata for visualization.
+ */
+interface ComposedSqore {
+  /** Width of visualization. */
+  width: number;
+  /** Height of visualization. */
+  height: number;
+  /** SVG elements the make up the visualization. */
+  elements: SVGElement[];
+}
+
+/**
+ * Defines the mapping of unique ID to each operation. Used for enabling
+ * interactivity.
+ */
+type GateRegistry = {
+  [id: string]: Operation;
+};
+
+/**
+ * Entrypoint class for rendering circuit visualizations.
+ */
+export class Sqore {
+  circuit: Circuit;
+  style: StyleConfig = {};
+  gateRegistry: GateRegistry = {};
+
+  /**
+   * Initializes Sqore object with custom styles.
+   *
+   * @param circuit Circuit to be visualized.
+   * @param style Custom visualization style.
+   */
+  constructor(circuit: Circuit, style: StyleConfig | string = {}) {
+    this.circuit = circuit;
+    this.style = this.getStyle(style);
+  }
+
+  /**
+   * Render circuit into `container` at the specified layer depth.
+   *
+   * @param container HTML element for rendering visualization into.
+   * @param renderDepth Initial layer depth at which to render gates.
+   */
+  draw(container: HTMLElement, renderDepth = 0): void {
+    // Inject into container
+    if (container == null) throw new Error(`Container not provided.`);
+
+    // Create copy of circuit to prevent mutation
+    const circuit: Circuit = JSON.parse(JSON.stringify(this.circuit));
+
+    // Assign unique IDs to each operation
+    circuit.operations.forEach((op, i) =>
+      this.fillGateRegistry(op, i.toString()),
+    );
+
+    // Render operations at starting at given depth
+    circuit.operations = this.selectOpsAtDepth(circuit.operations, renderDepth);
+
+    // If only one top-level operation, expand automatically:
+    if (
+      circuit.operations.length == 1 &&
+      circuit.operations[0].dataAttributes != null &&
+      // eslint-disable-next-line no-prototype-builtins
+      circuit.operations[0].dataAttributes.hasOwnProperty("id")
+    ) {
+      const id: string = circuit.operations[0].dataAttributes["id"];
+      this.expandOperation(circuit.operations, id);
+    }
+
+    this.renderCircuit(container, circuit);
+  }
+
+  /**
+   * Retrieve style for visualization.
+   *
+   * @param style Custom style or style name.
+   *
+   * @returns Custom style.
+   */
+  private getStyle(style: StyleConfig | string = {}): StyleConfig {
+    if (typeof style === "string" || style instanceof String) {
+      const styleName: string = style as string;
+      // eslint-disable-next-line no-prototype-builtins
+      if (!STYLES.hasOwnProperty(styleName)) {
+        console.error(`No style ${styleName} found in STYLES.`);
+        return {};
+      }
+      style = STYLES[styleName];
+    }
+    return style;
+  }
+
+  /**
+   * Render circuit into `container`.
+   *
+   * @param container HTML element for rendering visualization into.
+   * @param circuit Circuit object to be rendered.
+   */
+  private renderCircuit(container: HTMLElement, circuit: Circuit): void {
+    // Create visualization components
+    const composedSqore: ComposedSqore = this.compose(circuit);
+    const svg: SVGElement = this.generateSvg(composedSqore);
+    container.innerHTML = "";
+    container.appendChild(svg);
+    this.addGateClickHandlers(container, circuit);
+  }
+
+  /**
+   * Generates the components required for visualization.
+   *
+   * @param circuit Circuit to be visualized.
+   *
+   * @returns `ComposedSqore` object containing metadata for visualization.
+   */
+  private compose(circuit: Circuit): ComposedSqore {
+    const add = (acc: Metadata[], gate: Metadata | Metadata[]): void => {
+      if (Array.isArray(gate)) {
+        gate.forEach((g) => add(acc, g));
+      } else {
+        acc.push(gate);
+        gate.children?.forEach((g) => add(acc, g));
+      }
+    };
+
+    const flatten = (gates: Metadata[]): Metadata[] => {
+      const result: Metadata[] = [];
+      add(result, gates);
+      return result;
+    };
+
+    const { qubits, operations } = circuit;
+    const { qubitWires, registers, svgHeight } = formatInputs(qubits);
+    const { metadataList, svgWidth } = processOperations(operations, registers);
+    const formattedGates: SVGElement = formatGates(metadataList);
+    const measureGates: Metadata[] = flatten(metadataList).filter(
+      ({ type }) => type === GateType.Measure,
+    );
+    const formattedRegs: SVGElement = formatRegisters(
+      registers,
+      measureGates,
+      svgWidth,
+    );
+
+    const composedSqore: ComposedSqore = {
+      width: svgWidth,
+      height: svgHeight,
+      elements: [qubitWires, formattedRegs, formattedGates],
+    };
+    return composedSqore;
+  }
+
+  /**
+   * Generates visualization of `composedSqore` as an SVG.
+   *
+   * @param composedSqore ComposedSqore to be visualized.
+   *
+   * @returns SVG representation of circuit visualization.
+   */
+  private generateSvg(composedSqore: ComposedSqore): SVGElement {
+    const { width, height, elements } = composedSqore;
+    const uuid: string = createUUID();
+
+    const svg: SVGElement = document.createElementNS(svgNS, "svg");
+    svg.setAttribute("id", uuid);
+    svg.setAttribute("class", "qviz");
+    svg.setAttribute("width", width.toString());
+    svg.setAttribute("height", height.toString());
+    svg.style.setProperty("max-width", "fit-content");
+
+    // Add styles
+    const css = document.createElement("style");
+    css.innerHTML = style(this.style);
+    svg.appendChild(css);
+
+    // Add body elements
+    elements.forEach((element: SVGElement) => svg.appendChild(element));
+
+    return svg;
+  }
+
+  /**
+   * Depth-first traversal to assign unique ID to `operation`.
+   * The operation is assigned the id `id` and its `i`th child is recursively given
+   * the id `${id}-${i}`.
+   *
+   * @param operation Operation to be assigned.
+   * @param id: ID to assign to `operation`.
+   *
+   */
+  private fillGateRegistry(operation: Operation, id: string): void {
+    if (operation.dataAttributes == null) operation.dataAttributes = {};
+    operation.dataAttributes["id"] = id;
+    // By default, operations cannot be zoomed-out
+    operation.dataAttributes["zoom-out"] = "false";
+    this.gateRegistry[id] = operation;
+    operation.children?.forEach((childOp, i) => {
+      this.fillGateRegistry(childOp, `${id}-${i}`);
+      if (childOp.dataAttributes == null) childOp.dataAttributes = {};
+      // Children operations can be zoomed out
+      childOp.dataAttributes["zoom-out"] = "true";
+    });
+    // Composite operations can be zoomed in
+    operation.dataAttributes["zoom-in"] = (
+      operation.children != null
+    ).toString();
+  }
+
+  /**
+   * Pick out operations that are at or below `renderDepth`.
+   *
+   * @param operations List of circuit operations.
+   * @param renderDepth Initial layer depth at which to render gates.
+   *
+   * @returns List of operations at or below specifed depth.
+   */
+  private selectOpsAtDepth(
+    operations: Operation[],
+    renderDepth: number,
+  ): Operation[] {
+    if (renderDepth < 0)
+      throw new Error(
+        `Invalid renderDepth of ${renderDepth}. Needs to be >= 0.`,
+      );
+    if (renderDepth === 0) return operations;
+    return operations
+      .map((op) =>
+        op.children != null
+          ? this.selectOpsAtDepth(op.children, renderDepth - 1)
+          : op,
+      )
+      .flat();
+  }
+
+  /**
+   * Add interactive click handlers to circuit HTML elements.
+   *
+   * @param container HTML element containing visualized circuit.
+   * @param circuit Circuit to be visualized.
+   *
+   */
+  private addGateClickHandlers(container: HTMLElement, circuit: Circuit): void {
+    this.addClassicalControlHandlers(container);
+    this.addZoomHandlers(container, circuit);
+  }
+
+  /**
+   * Add interactive click handlers for classically-controlled operations.
+   *
+   * @param container HTML element containing visualized circuit.
+   *
+   */
+  private addClassicalControlHandlers(container: HTMLElement): void {
+    container.querySelectorAll(".classically-controlled-btn").forEach((btn) => {
+      // Zoom in on clicked gate
+      btn.addEventListener("click", (evt: Event) => {
+        const textSvg = btn.querySelector("text");
+        const group = btn.parentElement;
+        if (textSvg == null || group == null) return;
+
+        const currValue = textSvg.firstChild?.nodeValue;
+        const zeroGates = group?.querySelector(".gates-zero");
+        const oneGates = group?.querySelector(".gates-one");
+        switch (currValue) {
+          case "?":
+            textSvg.childNodes[0].nodeValue = "1";
+            group.classList.remove("classically-controlled-unknown");
+            group.classList.remove("classically-controlled-zero");
+            group.classList.add("classically-controlled-one");
+            zeroGates?.classList.add("hidden");
+            oneGates?.classList.remove("hidden");
+            break;
+          case "1":
+            textSvg.childNodes[0].nodeValue = "0";
+            group.classList.remove("classically-controlled-unknown");
+            group.classList.add("classically-controlled-zero");
+            group.classList.remove("classically-controlled-one");
+            zeroGates?.classList.remove("hidden");
+            oneGates?.classList.add("hidden");
+            break;
+          case "0":
+            textSvg.childNodes[0].nodeValue = "?";
+            group.classList.add("classically-controlled-unknown");
+            group.classList.remove("classically-controlled-zero");
+            group.classList.remove("classically-controlled-one");
+            zeroGates?.classList.remove("hidden");
+            oneGates?.classList.remove("hidden");
+            break;
+        }
+        evt.stopPropagation();
+      });
+    });
+  }
+
+  /**
+   * Add interactive click handlers for zoom-in/out functionality.
+   *
+   * @param container HTML element containing visualized circuit.
+   * @param circuit Circuit to be visualized.
+   *
+   */
+  private addZoomHandlers(container: HTMLElement, circuit: Circuit): void {
+    container.querySelectorAll(".gate .gate-control").forEach((ctrl) => {
+      // Zoom in on clicked gate
+      ctrl.addEventListener("click", (ev: Event) => {
+        const gateId: string | null | undefined =
+          ctrl.parentElement?.getAttribute("data-id");
+        if (typeof gateId == "string") {
+          if (ctrl.classList.contains("gate-collapse")) {
+            this.collapseOperation(circuit.operations, gateId);
+          } else if (ctrl.classList.contains("gate-expand")) {
+            this.expandOperation(circuit.operations, gateId);
+          }
+          this.renderCircuit(container, circuit);
+
+          ev.stopPropagation();
+        }
+      });
+    });
+  }
+
+  /**
+   * Expand selected operation for zoom-in interaction.
+   *
+   * @param operations List of circuit operations.
+   * @param id ID of operation to expand.
+   *
+   */
+  private expandOperation(operations: Operation[], id: string): void {
+    operations.forEach((op) => {
+      if (op.conditionalRender === ConditionalRender.AsGroup)
+        this.expandOperation(op.children || [], id);
+      if (op.dataAttributes == null) return op;
+      const opId: string = op.dataAttributes["id"];
+      if (opId === id && op.children != null) {
+        op.conditionalRender = ConditionalRender.AsGroup;
+        op.dataAttributes["expanded"] = "true";
+      }
+    });
+  }
+
+  /**
+   * Collapse selected operation for zoom-out interaction.
+   *
+   * @param operations List of circuit operations.
+   * @param id ID of operation to collapse.
+   *
+   */
+  private collapseOperation(operations: Operation[], parentId: string): void {
+    operations.forEach((op) => {
+      if (op.conditionalRender === ConditionalRender.AsGroup)
+        this.collapseOperation(op.children || [], parentId);
+      if (op.dataAttributes == null) return op;
+      const opId: string = op.dataAttributes["id"];
+      // Collapse parent gate and its children
+      if (opId.startsWith(parentId)) {
+        op.conditionalRender = ConditionalRender.Always;
+        delete op.dataAttributes["expanded"];
+      }
+    });
+  }
+}
diff --git a/npm/qsharp/ux/circuit-vis/styles.ts b/npm/qsharp/ux/circuit-vis/styles.ts
new file mode 100644
index 00000000..ad22c45b
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/styles.ts
@@ -0,0 +1,236 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+/**
+ * Provides configuration for CSS styles of visualization.
+ */
+export interface StyleConfig {
+  /** Line stroke style. */
+  lineStroke?: string;
+  /** Line width. */
+  lineWidth?: number;
+  /** Text colour. */
+  textColour?: string;
+  /** Single qubit unitary fill colour. */
+  unitary?: string;
+  /** Oplus circle fill colour. */
+  oplus?: string;
+  /** Measurement gate fill colour. */
+  measure?: string;
+  /** Measurement unknown primary colour. */
+  classicalUnknown?: string;
+  /** Measurement zero primary colour. */
+  classicalZero?: string;
+  /** Measurement one primary colour. */
+  classicalOne?: string;
+  /** Measurement zero text colour */
+  classicalZeroText?: string;
+  /** Measurement one text colour */
+  classicalOneText?: string;
+}
+
+const defaultStyle: StyleConfig = {
+  lineStroke: "#000000",
+  lineWidth: 1,
+  textColour: "#000000",
+  unitary: "#D9F1FA",
+  oplus: "#FFFFFF",
+  measure: "#FFDE86",
+  classicalUnknown: "#E5E5E5",
+  classicalZero: "#C40000",
+  classicalOne: "#4059BD",
+  classicalZeroText: "#FFFFFF",
+  classicalOneText: "#FFFFFF",
+};
+
+const blackAndWhiteStyle: StyleConfig = {
+  lineStroke: "#000000",
+  lineWidth: 1,
+  textColour: "#000000",
+  unitary: "#FFFFFF",
+  oplus: "#FFFFFF",
+  measure: "#FFFFFF",
+  classicalUnknown: "#FFFFFF",
+  classicalZero: "#000000",
+  classicalOne: "#000000",
+  classicalZeroText: "#FFFFFF",
+  classicalOneText: "#FFFFFF",
+};
+
+const invertedStyle: StyleConfig = {
+  lineStroke: "#FFFFFF",
+  lineWidth: 1,
+  textColour: "#FFFFFF",
+  unitary: "#000000",
+  oplus: "#000000",
+  measure: "#000000",
+  classicalUnknown: "#000000",
+  classicalZero: "#FFFFFF",
+  classicalOne: "#FFFFFF",
+  classicalZeroText: "#000000",
+  classicalOneText: "#000000",
+};
+
+/**
+ * Set of default styles.
+ */
+export const STYLES: { [name: string]: StyleConfig } = {
+  /** Default style with coloured gates. */
+  Default: defaultStyle,
+  /** Black and white style. */
+  BlackAndWhite: blackAndWhiteStyle,
+  /** Inverted black and white style (for black backgrounds). */
+  Inverted: invertedStyle,
+};
+
+/**
+ * CSS style script to be injected into visualization SVG.
+ *
+ * @param customStyle Custom style configuration.
+ *
+ * @returns String containing CSS style script.
+ */
+export const style = (customStyle: StyleConfig = {}): string => {
+  const styleConfig = { ...defaultStyle, ...customStyle };
+
+  return `${_defaultGates(styleConfig)}
+    ${_classicallyControlledGates(styleConfig)}
+    ${_expandCollapse}`;
+};
+
+const _defaultGates = (styleConfig: StyleConfig): string => `
+    line,
+    circle,
+    rect {
+        stroke: ${styleConfig.lineStroke};
+        stroke-width: ${styleConfig.lineWidth};
+    }
+    text {
+        fill: ${styleConfig.textColour};
+        dominant-baseline: middle;
+        text-anchor: middle;
+        font-family: Arial;
+    }
+    .control-dot {
+        fill: ${styleConfig.lineStroke};
+    }
+    .oplus line, .oplus circle {
+        fill: ${styleConfig.oplus};
+        stroke-width: 2;
+    }
+    .gate-unitary {
+        fill: ${styleConfig.unitary};
+    }
+    .gate-measure {
+        fill: ${styleConfig.measure};
+    }
+    rect.gate-swap {
+        fill: transparent;
+        stroke: transparent;
+    }
+    .arc-measure {
+        stroke: ${styleConfig.lineStroke};
+        fill: none;
+        stroke-width: ${styleConfig.lineWidth};
+    }
+    .register-classical {
+        stroke-width: ${(styleConfig.lineWidth || 0) / 2};
+    }`;
+
+const _classicallyControlledGates = (styleConfig: StyleConfig): string => {
+  const gateOutline = `
+    .classically-controlled-one .classical-container,
+    .classically-controlled-one .classical-line {
+        stroke: ${styleConfig.classicalOne};
+        stroke-width: ${(styleConfig.lineWidth || 0) + 0.3};
+        fill: ${styleConfig.classicalOne};
+        fill-opacity: 0.1;
+    }
+    .classically-controlled-zero .classical-container,
+    .classically-controlled-zero .classical-line {
+        stroke: ${styleConfig.classicalZero};
+        stroke-width: ${(styleConfig.lineWidth || 0) + 0.3};
+        fill: ${styleConfig.classicalZero};
+        fill-opacity: 0.1;
+    }`;
+  const controlBtn = `
+    .classically-controlled-btn {
+        cursor: pointer;
+    }
+    .classically-controlled-unknown .classically-controlled-btn {
+        fill: ${styleConfig.classicalUnknown};
+    }
+    .classically-controlled-one .classically-controlled-btn {
+        fill: ${styleConfig.classicalOne};
+    }
+    .classically-controlled-zero .classically-controlled-btn {
+        fill: ${styleConfig.classicalZero};
+    }`;
+
+  const controlBtnText = `
+    .classically-controlled-btn text {
+        dominant-baseline: middle;
+        text-anchor: middle;
+        stroke: none;
+        font-family: Arial;
+    }
+    .classically-controlled-unknown .classically-controlled-btn text {
+        fill: ${styleConfig.textColour};
+    }
+    .classically-controlled-one .classically-controlled-btn text {
+        fill: ${styleConfig.classicalOneText};
+    }
+    .classically-controlled-zero .classically-controlled-btn text {
+        fill: ${styleConfig.classicalZeroText};
+    }`;
+
+  return `
+    .hidden {
+        display: none;
+    }
+    .classically-controlled-unknown {
+        opacity: 0.25;
+    }
+
+    ${gateOutline}
+    ${controlBtn}
+    ${controlBtnText}`;
+};
+
+const _expandCollapse = `
+    .qviz .gate-collapse,
+    .qviz .gate-expand {
+        opacity: 0;
+        transition: opacity 1s;
+    }
+
+    .qviz:hover .gate-collapse,
+    .qviz:hover .gate-expand {
+        visibility: visible;
+        opacity: 0.2;
+        transition: visibility 1s;
+        transition: opacity 1s;
+    }
+
+    .gate-expand, .gate-collapse {
+        cursor: pointer;
+    }
+
+    .gate-collapse circle,
+    .gate-expand circle {
+        fill: white;
+        stroke-width: 2px;
+        stroke: black;
+    }
+    .gate-collapse path,
+    .gate-expand path {
+        stroke-width: 4px;
+        stroke: black;
+    }
+
+    .gate:hover > .gate-collapse,
+    .gate:hover > .gate-expand {
+        visibility: visible;
+        opacity: 1;
+        transition: opacity 1s;
+    }`;
diff --git a/npm/qsharp/ux/circuit-vis/utils.ts b/npm/qsharp/ux/circuit-vis/utils.ts
new file mode 100644
index 00000000..8ce9f9d4
--- /dev/null
+++ b/npm/qsharp/ux/circuit-vis/utils.ts
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Metadata, GateType } from "./metadata";
+import {
+  minGateWidth,
+  labelPadding,
+  labelFontSize,
+  argsFontSize,
+} from "./constants";
+
+/**
+ * Generate a UUID using `Math.random`.
+ * Note: this implementation came from https://stackoverflow.com/questions/105034/how-to-create-guid-uuid
+ * and is not cryptographically secure but works for our use case.
+ *
+ * @returns UUID string.
+ */
+const createUUID = (): string =>
+  "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
+    const r = (Math.random() * 16) | 0,
+      v = c == "x" ? r : (r & 0x3) | 0x8;
+    return v.toString(16);
+  });
+
+/**
+ * Calculate the width of a gate, given its metadata.
+ *
+ * @param metadata Metadata of a given gate.
+ *
+ * @returns Width of given gate (in pixels).
+ */
+const getGateWidth = ({
+  type,
+  label,
+  displayArgs,
+  width,
+}: Metadata): number => {
+  if (width > 0) return width;
+
+  switch (type) {
+    case GateType.Measure:
+    case GateType.Cnot:
+    case GateType.Swap:
+      return minGateWidth;
+    default: {
+      const labelWidth = _getStringWidth(label);
+      const argsWidth =
+        displayArgs != null ? _getStringWidth(displayArgs, argsFontSize) : 0;
+      const textWidth = Math.max(labelWidth, argsWidth) + labelPadding * 2;
+      return Math.max(minGateWidth, textWidth);
+    }
+  }
+};
+
+/**
+ * Get the width of a string with font-size `fontSize` and font-family Arial.
+ *
+ * @param text     Input string.
+ * @param fontSize Font size of `text`.
+ *
+ * @returns Pixel width of given string.
+ */
+const _getStringWidth = (
+  text: string,
+  fontSize: number = labelFontSize,
+): number => {
+  const canvas: HTMLCanvasElement = document.createElement("canvas");
+  const context: CanvasRenderingContext2D | null = canvas.getContext("2d");
+  if (context == null) throw new Error("Null canvas");
+
+  context.font = `${fontSize}px Arial`;
+  const metrics: TextMetrics = context.measureText(text);
+  return metrics.width;
+};
+
+export { createUUID, getGateWidth, _getStringWidth };
diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx
index 6b5e0861..a4bab823 100644
--- a/npm/qsharp/ux/circuit.tsx
+++ b/npm/qsharp/ux/circuit.tsx
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import * as qviz from "@microsoft/quantum-viz.js/lib";
+import * as qviz from "./circuit-vis";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { CircuitProps } from "./data.js";
 import { Spinner } from "./spinner.js";
@@ -131,7 +131,7 @@ function ZoomableCircuit(props: { circuit: qviz.Circuit }) {
   function renderCircuit(circuit: qviz.Circuit, container: HTMLDivElement) {
     qviz.draw(circuit, container);
 
-    // quantum-viz hardcodes the styles in the SVG.
+    // circuit-vis hardcodes the styles in the SVG.
     // Remove the style elements -- we'll define the styles in our own CSS.
     const styleElements = container.querySelectorAll("style");
     styleElements?.forEach((tag) => tag.remove());
diff --git a/npm/qsharp/ux/data.ts b/npm/qsharp/ux/data.ts
index 72a3ab4b..8f4c9f33 100644
--- a/npm/qsharp/ux/data.ts
+++ b/npm/qsharp/ux/data.ts
@@ -75,4 +75,4 @@ export type CircuitProps = {
   calculating: boolean;
 };
 
-export type CircuitData = import("@microsoft/quantum-viz.js/lib").Circuit;
+export type CircuitData = import("../src/shared/circuit").Circuit;
diff --git a/package-lock.json b/package-lock.json
index 38045a9f..2e8fec50 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,6 @@
       "devDependencies": {
         "@anywidget/types": "^0.1.4",
         "@eslint/js": "^9.9.1",
-        "@microsoft/quantum-viz.js": "^1.0.5",
         "@parcel/watcher": "^2.4.1",
         "@types/chai": "^4.3.8",
         "@types/markdown-it": "^14.1.1",
@@ -28,13 +27,13 @@
         "@vscode/markdown-it-katex": "^1.0.0",
         "@vscode/test-web": "^0.0.61",
         "chai": "^4.3.10",
-        "esbuild": "^0.23.1",
+        "esbuild": "^0.25.0",
         "eslint": "^9.17.0",
         "events": "^3.3.0",
         "github-markdown-css": "^5.3.0",
         "globals": "^15.9.0",
         "markdown-it": "^14.1.0",
-        "mocha": "^10.2.0",
+        "mocha": "^10.8.2",
         "modern-normalize": "^2.0.0",
         "monaco-editor": "^0.44.0",
         "preact": "^10.20.0",
@@ -233,13 +232,14 @@
       }
     },
     "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
-      "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+      "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
       "cpu": [
         "ppc64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "aix"
@@ -249,13 +249,14 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
-      "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+      "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
       "cpu": [
         "arm"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -265,13 +266,14 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
-      "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
+      "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -281,13 +283,14 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
-      "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
+      "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -297,13 +300,14 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
-      "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
+      "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "darwin"
@@ -313,13 +317,14 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
-      "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
+      "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "darwin"
@@ -329,13 +334,14 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
-      "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "freebsd"
@@ -345,13 +351,14 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
-      "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
+      "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "freebsd"
@@ -361,13 +368,14 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
-      "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
+      "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
       "cpu": [
         "arm"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -377,13 +385,14 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
-      "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
+      "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -393,13 +402,14 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
-      "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
+      "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
       "cpu": [
         "ia32"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -409,13 +419,14 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
-      "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
+      "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
       "cpu": [
         "loong64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -425,13 +436,14 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
-      "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
+      "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
       "cpu": [
         "mips64el"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -441,13 +453,14 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
-      "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
+      "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
       "cpu": [
         "ppc64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -457,13 +470,14 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
-      "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
+      "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
       "cpu": [
         "riscv64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -473,13 +487,14 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
-      "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
+      "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
       "cpu": [
         "s390x"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -489,13 +504,14 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
-      "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
+      "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -504,14 +520,32 @@
         "node": ">=18"
       }
     },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
-      "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "netbsd"
@@ -521,13 +555,14 @@
       }
     },
     "node_modules/@esbuild/openbsd-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
-      "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "openbsd"
@@ -537,13 +572,14 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
-      "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "openbsd"
@@ -553,13 +589,14 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
-      "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
+      "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "sunos"
@@ -569,13 +606,14 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
-      "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
+      "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -585,13 +623,14 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
-      "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
+      "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
       "cpu": [
         "ia32"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -601,13 +640,14 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
-      "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
+      "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
       "cpu": [
         "x64"
       ],
       "dev": true,
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -1451,12 +1491,6 @@
       "integrity": "sha512-gNw9z9LbqLV+WadZ6/MMrWwO3e0LuoUH1wve/1iPsBNbgqeVCiB0EZFNNj2lysxS2gkqoF9hmyVaG3MoM1BkxA==",
       "dev": true
     },
-    "node_modules/@microsoft/quantum-viz.js": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@microsoft/quantum-viz.js/-/quantum-viz.js-1.0.5.tgz",
-      "integrity": "sha512-U2atugL4j+p/yiioPR/TfqqR534iP6kEa10S+YNUW68UyvMe8eXNDFlb8GG4Ro9WSGobyLa8tP7iRiR2YMZO1w==",
-      "dev": true
-    },
     "node_modules/@nevware21/ts-async": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.0.tgz",
@@ -2382,10 +2416,11 @@
       }
     },
     "node_modules/ansi-colors": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
-      "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+      "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=6"
       }
@@ -2975,12 +3010,13 @@
       }
     },
     "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "ms": "2.1.2"
+        "ms": "^2.1.3"
       },
       "engines": {
         "node": ">=6.0"
@@ -3109,10 +3145,11 @@
       }
     },
     "node_modules/diff": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
-      "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+      "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.3.1"
       }
@@ -3208,11 +3245,12 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
-      "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+      "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
       "dev": true,
       "hasInstallScript": true,
+      "license": "MIT",
       "bin": {
         "esbuild": "bin/esbuild"
       },
@@ -3220,30 +3258,31 @@
         "node": ">=18"
       },
       "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.23.1",
-        "@esbuild/android-arm": "0.23.1",
-        "@esbuild/android-arm64": "0.23.1",
-        "@esbuild/android-x64": "0.23.1",
-        "@esbuild/darwin-arm64": "0.23.1",
-        "@esbuild/darwin-x64": "0.23.1",
-        "@esbuild/freebsd-arm64": "0.23.1",
-        "@esbuild/freebsd-x64": "0.23.1",
-        "@esbuild/linux-arm": "0.23.1",
-        "@esbuild/linux-arm64": "0.23.1",
-        "@esbuild/linux-ia32": "0.23.1",
-        "@esbuild/linux-loong64": "0.23.1",
-        "@esbuild/linux-mips64el": "0.23.1",
-        "@esbuild/linux-ppc64": "0.23.1",
-        "@esbuild/linux-riscv64": "0.23.1",
-        "@esbuild/linux-s390x": "0.23.1",
-        "@esbuild/linux-x64": "0.23.1",
-        "@esbuild/netbsd-x64": "0.23.1",
-        "@esbuild/openbsd-arm64": "0.23.1",
-        "@esbuild/openbsd-x64": "0.23.1",
-        "@esbuild/sunos-x64": "0.23.1",
-        "@esbuild/win32-arm64": "0.23.1",
-        "@esbuild/win32-ia32": "0.23.1",
-        "@esbuild/win32-x64": "0.23.1"
+        "@esbuild/aix-ppc64": "0.25.0",
+        "@esbuild/android-arm": "0.25.0",
+        "@esbuild/android-arm64": "0.25.0",
+        "@esbuild/android-x64": "0.25.0",
+        "@esbuild/darwin-arm64": "0.25.0",
+        "@esbuild/darwin-x64": "0.25.0",
+        "@esbuild/freebsd-arm64": "0.25.0",
+        "@esbuild/freebsd-x64": "0.25.0",
+        "@esbuild/linux-arm": "0.25.0",
+        "@esbuild/linux-arm64": "0.25.0",
+        "@esbuild/linux-ia32": "0.25.0",
+        "@esbuild/linux-loong64": "0.25.0",
+        "@esbuild/linux-mips64el": "0.25.0",
+        "@esbuild/linux-ppc64": "0.25.0",
+        "@esbuild/linux-riscv64": "0.25.0",
+        "@esbuild/linux-s390x": "0.25.0",
+        "@esbuild/linux-x64": "0.25.0",
+        "@esbuild/netbsd-arm64": "0.25.0",
+        "@esbuild/netbsd-x64": "0.25.0",
+        "@esbuild/openbsd-arm64": "0.25.0",
+        "@esbuild/openbsd-x64": "0.25.0",
+        "@esbuild/sunos-x64": "0.25.0",
+        "@esbuild/win32-arm64": "0.25.0",
+        "@esbuild/win32-ia32": "0.25.0",
+        "@esbuild/win32-x64": "0.25.0"
       }
     },
     "node_modules/escalade": {
@@ -4753,31 +4792,32 @@
       }
     },
     "node_modules/mocha": {
-      "version": "10.4.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz",
-      "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-colors": "4.1.1",
-        "browser-stdout": "1.3.1",
-        "chokidar": "3.5.3",
-        "debug": "4.3.4",
-        "diff": "5.0.0",
-        "escape-string-regexp": "4.0.0",
-        "find-up": "5.0.0",
-        "glob": "8.1.0",
-        "he": "1.2.0",
-        "js-yaml": "4.1.0",
-        "log-symbols": "4.1.0",
-        "minimatch": "5.0.1",
-        "ms": "2.1.3",
-        "serialize-javascript": "6.0.0",
-        "strip-json-comments": "3.1.1",
-        "supports-color": "8.1.1",
-        "workerpool": "6.2.1",
-        "yargs": "16.2.0",
-        "yargs-parser": "20.2.4",
-        "yargs-unparser": "2.0.0"
+      "version": "10.8.2",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
+      "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-colors": "^4.1.3",
+        "browser-stdout": "^1.3.1",
+        "chokidar": "^3.5.3",
+        "debug": "^4.3.5",
+        "diff": "^5.2.0",
+        "escape-string-regexp": "^4.0.0",
+        "find-up": "^5.0.0",
+        "glob": "^8.1.0",
+        "he": "^1.2.0",
+        "js-yaml": "^4.1.0",
+        "log-symbols": "^4.1.0",
+        "minimatch": "^5.1.6",
+        "ms": "^2.1.3",
+        "serialize-javascript": "^6.0.2",
+        "strip-json-comments": "^3.1.1",
+        "supports-color": "^8.1.1",
+        "workerpool": "^6.5.1",
+        "yargs": "^16.2.0",
+        "yargs-parser": "^20.2.9",
+        "yargs-unparser": "^2.0.0"
       },
       "bin": {
         "_mocha": "bin/_mocha",
@@ -4788,10 +4828,11 @@
       }
     },
     "node_modules/mocha/node_modules/minimatch": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
-      "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
       "dev": true,
+      "license": "ISC",
       "dependencies": {
         "brace-expansion": "^2.0.1"
       },
@@ -4799,12 +4840,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/mocha/node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true
-    },
     "node_modules/mocha/node_modules/supports-color": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -4888,10 +4923,11 @@
       }
     },
     "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/natural-compare": {
       "version": "1.4.0",
@@ -5340,6 +5376,7 @@
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "safe-buffer": "^5.1.0"
       }
@@ -5587,10 +5624,11 @@
       }
     },
     "node_modules/serialize-javascript": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-      "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
         "randombytes": "^2.1.0"
       }
@@ -6129,10 +6167,11 @@
       }
     },
     "node_modules/workerpool": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
-      "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
-      "dev": true
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+      "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+      "dev": true,
+      "license": "Apache-2.0"
     },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
@@ -6259,10 +6298,11 @@
       }
     },
     "node_modules/yargs-parser": {
-      "version": "20.2.4",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-      "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
       "dev": true,
+      "license": "ISC",
       "engines": {
         "node": ">=10"
       }
diff --git a/package.json b/package.json
index 13e25711..a6e6a7d0 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,6 @@
   "devDependencies": {
     "@anywidget/types": "^0.1.4",
     "@eslint/js": "^9.9.1",
-    "@microsoft/quantum-viz.js": "^1.0.5",
     "@parcel/watcher": "^2.4.1",
     "@types/chai": "^4.3.8",
     "@types/markdown-it": "^14.1.1",
@@ -33,13 +32,13 @@
     "@vscode/markdown-it-katex": "^1.0.0",
     "@vscode/test-web": "^0.0.61",
     "chai": "^4.3.10",
-    "esbuild": "^0.23.1",
+    "esbuild": "^0.25.0",
     "eslint": "^9.17.0",
     "events": "^3.3.0",
     "github-markdown-css": "^5.3.0",
     "globals": "^15.9.0",
     "markdown-it": "^14.1.0",
-    "mocha": "^10.2.0",
+    "mocha": "^10.8.2",
     "modern-normalize": "^2.0.0",
     "monaco-editor": "^0.44.0",
     "preact": "^10.20.0",
diff --git a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs
index 60fefe17..b23be237 100644
--- a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs
+++ b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs
@@ -107,7 +107,10 @@ impl<
                 ));
             }
 
-            num_cycles = max_allowed_num_cycles_for_code_parameter + 1;
+            num_cycles = std::cmp::max(
+                max_allowed_num_cycles_for_code_parameter + 1,
+                num_cycles + 1,
+            );
         }
     }
 
diff --git a/samples/estimation/df-chemistry/src/Prepare.qs b/samples/estimation/df-chemistry/src/Prepare.qs
index 504b5652..3ae7de04 100644
--- a/samples/estimation/df-chemistry/src/Prepare.qs
+++ b/samples/estimation/df-chemistry/src/Prepare.qs
@@ -7,6 +7,7 @@ import Std.Intrinsic.*;
 import Std.Math.*;
 import Std.Arithmetic.*;
 import Std.TableLookup.*;
+import Std.StatePreparation.PrepareUniformSuperposition;
 
 // ------------------------------------- //
 // State preparation (public operations) //
@@ -19,37 +20,6 @@ operation PrepareSingleQubit(p0 : Double, p1 : Double, target : Qubit) : Unit is
     Ry(2.0 * alpha, target);
 }
 
-operation PrepareUniformSuperposition(numStates : Int, qs : Qubit[]) : Unit is Adj + Ctl {
-    Fact(numStates >= 1, "numStates must be positive");
-    Fact(numStates <= 2^Length(qs), $"numStates must be smaller or equal to {2^Length(qs)}");
-
-    let qsAdjusted = qs[...Ceiling(Lg(IntAsDouble(numStates))) - 1];
-
-    let (factor, pow) = DecomposePowerOf2(numStates);
-
-    if factor == 1 {
-        ApplyToEachCA(H, qsAdjusted[0..pow - 1]);
-    } else {
-        use tgt = Qubit();
-
-        let sqrt = Sqrt(IntAsDouble(1 <<< Length(qsAdjusted)) / IntAsDouble(numStates));
-        let angle = 2.0 * ArcSin(0.5 * sqrt);
-
-        ApplyToEachCA(H, qsAdjusted);
-
-        ApplyIfGreaterL(Ry(2.0 * angle, _), IntAsBigInt(numStates), qsAdjusted, tgt);
-
-        within {
-            ApplyToEachA(H, qsAdjusted[pow...]);
-        } apply {
-            ReflectAboutInteger(0, qsAdjusted[pow...] + [tgt]);
-            Ry(-angle, tgt);
-        }
-
-        X(tgt);
-    }
-}
-
 struct PrepareArbitrarySuperposition {
     NIndexQubits : Int,
     NGarbageQubits : Int,
@@ -87,18 +57,6 @@ function MakePrepareArbitrarySuperpositionWithData(targetError : Double, coeffic
 // State preparation (private operations) //
 // -------------------------------------- //
 
-internal function DecomposePowerOf2(number : Int) : (Int, Int) {
-    mutable pow = 0;
-    mutable factor = number;
-
-    while factor % 2 == 0 {
-        set factor /= 2;
-        set pow += 1;
-    }
-
-    (factor, pow)
-}
-
 internal function ArbitrarySuperpositionRegisterLengths(targetError : Double, nCoefficients : Int) : (Int, Int) {
     Fact(targetError > 0.0, "targetError must be positive");
     Fact(nCoefficients > 0, "nCoefficients must be positive");
diff --git a/vscode/src/azure/workspaceActions.ts b/vscode/src/azure/workspaceActions.ts
index 964c8a31..9066b074 100644
--- a/vscode/src/azure/workspaceActions.ts
+++ b/vscode/src/azure/workspaceActions.ts
@@ -104,7 +104,7 @@ export function getPythonCodeForWorkspace(
 
   const pythonCode = `# If developing locally, on first run this will open a browser to authenticate the
 # connection with Azure. In remote scenarios, such as SSH or Codespaces, it may
-# be necesssary to install the Azure CLI and run 'az login --use-device-code' to
+# be necessary to install the Azure CLI and run 'az login --use-device-code' to
 # authenticate. For unattended scenarios, such as batch jobs, a service principal
 # should be configured and used for authentication. For more information, see
 # https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview
diff --git a/vscode/src/circuit.ts b/vscode/src/circuit.ts
index c26e3250..159cf998 100644
--- a/vscode/src/circuit.ts
+++ b/vscode/src/circuit.ts
@@ -1,9 +1,9 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import { type Circuit as CircuitData } from "@microsoft/quantum-viz.js/lib";
 import { escapeHtml } from "markdown-it/lib/common/utils.mjs";
 import {
+  type CircuitData,
   ICompilerWorker,
   IOperationInfo,
   IQSharpError,
diff --git a/vscode/src/debugger/activate.ts b/vscode/src/debugger/activate.ts
index 5a7296f4..ee0bd4c0 100644
--- a/vscode/src/debugger/activate.ts
+++ b/vscode/src/debugger/activate.ts
@@ -45,17 +45,33 @@ function registerCommands(context: vscode.ExtensionContext) {
   context.subscriptions.push(
     vscode.commands.registerCommand(
       `${qsharpExtensionId}.runEditorContents`,
-      (resource: vscode.Uri) =>
+      (resource: vscode.Uri, expr?: string) => {
+        // if expr is not a string, ignore it. VS Code can sometimes
+        // pass other types when this command is invoked via UI buttons.
+        if (typeof expr !== "string") {
+          expr = undefined;
+        }
         startDebugging(
           resource,
-          { name: "Run Q# File", stopOnEntry: false },
+          { name: "Run Q# File", stopOnEntry: false, entry: expr },
           { noDebug: true },
-        ),
+        );
+      },
     ),
     vscode.commands.registerCommand(
       `${qsharpExtensionId}.debugEditorContents`,
-      (resource: vscode.Uri) =>
-        startDebugging(resource, { name: "Debug Q# File", stopOnEntry: true }),
+      (resource: vscode.Uri, expr?: string) => {
+        // if expr is not a string, ignore it. VS Code can sometimes
+        // pass other types when this command is invoked via UI buttons.
+        if (typeof expr !== "string") {
+          expr = undefined;
+        }
+        startDebugging(resource, {
+          name: "Debug Q# File",
+          stopOnEntry: true,
+          entry: expr,
+        });
+      },
     ),
     vscode.commands.registerCommand(
       `${qsharpExtensionId}.runEditorContentsWithCircuit`,
@@ -73,7 +89,7 @@ function registerCommands(context: vscode.ExtensionContext) {
   );
 
   function startDebugging(
-    resource: vscode.Uri,
+    resource: vscode.Uri | undefined,
     config: { name: string; [key: string]: any },
     options?: vscode.DebugSessionOptions,
   ) {
@@ -147,6 +163,7 @@ class QsDebugConfigProvider implements vscode.DebugConfigurationProvider {
         config.shots = 1;
         config.noDebug = "noDebug" in config ? config.noDebug : false;
         config.stopOnEntry = !config.noDebug;
+        config.entry = config.entry ?? "";
       }
     }
 
diff --git a/vscode/src/language-service/codeLens.ts b/vscode/src/language-service/codeLens.ts
index f5d95223..d7b2fe9e 100644
--- a/vscode/src/language-service/codeLens.ts
+++ b/vscode/src/language-service/codeLens.ts
@@ -39,7 +39,6 @@ function mapCodeLens(cl: ICodeLens): vscode.CodeLens {
   let command;
   let title;
   let tooltip;
-  let args = undefined;
   switch (cl.command) {
     case "histogram":
       title = "Histogram";
@@ -54,27 +53,24 @@ function mapCodeLens(cl: ICodeLens): vscode.CodeLens {
     case "debug":
       title = "Debug";
       command = "qsharp-vscode.debugEditorContents";
-      tooltip = "Debug program";
+      tooltip = "Debug callable";
       break;
     case "run":
       title = "Run";
       command = "qsharp-vscode.runEditorContents";
-      tooltip = "Run program";
+      tooltip = "Run callable";
       break;
     case "circuit":
       title = "Circuit";
       command = "qsharp-vscode.showCircuit";
       tooltip = "Show circuit";
-      if (cl.args) {
-        args = [cl.args];
-      }
       break;
   }
 
   return new vscode.CodeLens(toVsCodeRange(cl.range), {
     title,
     command,
-    arguments: args,
+    arguments: cl.args ?? [],
     tooltip,
   });
 }
diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts
index e8aa02de..e1adf6a3 100644
--- a/vscode/src/webviewPanel.ts
+++ b/vscode/src/webviewPanel.ts
@@ -1,6 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+import * as vscode from "vscode";
 import {
   IOperationInfo,
   QscEventTarget,
@@ -44,229 +45,233 @@ export function registerWebViewCommands(context: ExtensionContext) {
   ).toString();
 
   context.subscriptions.push(
-    commands.registerCommand(`${qsharpExtensionId}.showRe`, async () => {
-      clearCommandDiagnostics();
-      const associationId = getRandomGuid();
-      sendTelemetryEvent(
-        EventType.TriggerResourceEstimation,
-        { associationId },
-        {},
-      );
-      const program = await getActiveProgram();
-      if (!program.success) {
-        throw new Error(program.errorMsg);
-      }
+    commands.registerCommand(
+      `${qsharpExtensionId}.showRe`,
+      async (resource?: vscode.Uri, expr?: string) => {
+        clearCommandDiagnostics();
+        const associationId = getRandomGuid();
+        sendTelemetryEvent(
+          EventType.TriggerResourceEstimation,
+          { associationId },
+          {},
+        );
+        const program = await getActiveProgram();
+        if (!program.success) {
+          throw new Error(program.errorMsg);
+        }
 
-      const qubitType = await window.showQuickPick(
-        [
-          {
-            label: "qubit_gate_ns_e3",
-            detail: "Superconducting/spin qubit with 1e-3 error rate",
-            picked: true,
-            params: {
-              qubitParams: { name: "qubit_gate_ns_e3" },
-              qecScheme: { name: "surface_code" },
+        const qubitType = await window.showQuickPick(
+          [
+            {
+              label: "qubit_gate_ns_e3",
+              detail: "Superconducting/spin qubit with 1e-3 error rate",
+              picked: true,
+              params: {
+                qubitParams: { name: "qubit_gate_ns_e3" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_gate_ns_e4",
-            detail: "Superconducting/spin qubit with 1e-4 error rate",
-            params: {
-              qubitParams: { name: "qubit_gate_ns_e4" },
-              qecScheme: { name: "surface_code" },
+            {
+              label: "qubit_gate_ns_e4",
+              detail: "Superconducting/spin qubit with 1e-4 error rate",
+              params: {
+                qubitParams: { name: "qubit_gate_ns_e4" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_gate_us_e3",
-            detail: "Trapped ion qubit with 1e-3 error rate",
-            params: {
-              qubitParams: { name: "qubit_gate_us_e3" },
-              qecScheme: { name: "surface_code" },
+            {
+              label: "qubit_gate_us_e3",
+              detail: "Trapped ion qubit with 1e-3 error rate",
+              params: {
+                qubitParams: { name: "qubit_gate_us_e3" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_gate_us_e4",
-            detail: "Trapped ion qubit with 1e-4 error rate",
-            params: {
-              qubitParams: { name: "qubit_gate_us_e4" },
-              qecScheme: { name: "surface_code" },
+            {
+              label: "qubit_gate_us_e4",
+              detail: "Trapped ion qubit with 1e-4 error rate",
+              params: {
+                qubitParams: { name: "qubit_gate_us_e4" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_maj_ns_e4 + surface_code",
-            detail: "Majorana qubit with 1e-4 error rate (surface code QEC)",
-            params: {
-              qubitParams: { name: "qubit_maj_ns_e4" },
-              qecScheme: { name: "surface_code" },
+            {
+              label: "qubit_maj_ns_e4 + surface_code",
+              detail: "Majorana qubit with 1e-4 error rate (surface code QEC)",
+              params: {
+                qubitParams: { name: "qubit_maj_ns_e4" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_maj_ns_e6 + surface_code",
-            detail: "Majorana qubit with 1e-6 error rate (surface code QEC)",
-            params: {
-              qubitParams: { name: "qubit_maj_ns_e6" },
-              qecScheme: { name: "surface_code" },
+            {
+              label: "qubit_maj_ns_e6 + surface_code",
+              detail: "Majorana qubit with 1e-6 error rate (surface code QEC)",
+              params: {
+                qubitParams: { name: "qubit_maj_ns_e6" },
+                qecScheme: { name: "surface_code" },
+              },
             },
-          },
-          {
-            label: "qubit_maj_ns_e4 + floquet_code",
-            detail: "Majorana qubit with 1e-4 error rate (floquet code QEC)",
-            params: {
-              qubitParams: { name: "qubit_maj_ns_e4" },
-              qecScheme: { name: "floquet_code" },
+            {
+              label: "qubit_maj_ns_e4 + floquet_code",
+              detail: "Majorana qubit with 1e-4 error rate (floquet code QEC)",
+              params: {
+                qubitParams: { name: "qubit_maj_ns_e4" },
+                qecScheme: { name: "floquet_code" },
+              },
             },
-          },
-          {
-            label: "qubit_maj_ns_e6 + floquet_code",
-            detail: "Majorana qubit with 1e-6 error rate (floquet code QEC)",
-            params: {
-              qubitParams: { name: "qubit_maj_ns_e6" },
-              qecScheme: { name: "floquet_code" },
+            {
+              label: "qubit_maj_ns_e6 + floquet_code",
+              detail: "Majorana qubit with 1e-6 error rate (floquet code QEC)",
+              params: {
+                qubitParams: { name: "qubit_maj_ns_e6" },
+                qecScheme: { name: "floquet_code" },
+              },
             },
+          ],
+          {
+            canPickMany: true,
+            title: "Qubit types",
+            placeHolder: "Superconducting/spin qubit with 1e-3 error rate",
+            matchOnDetail: true,
           },
-        ],
-        {
-          canPickMany: true,
-          title: "Qubit types",
-          placeHolder: "Superconducting/spin qubit with 1e-3 error rate",
-          matchOnDetail: true,
-        },
-      );
-
-      if (!qubitType) {
-        return;
-      }
+        );
 
-      // Prompt for error budget (default to 0.001)
-      const validateErrorBudget = (input: string) => {
-        const result = parseFloat(input);
-        if (isNaN(result) || result <= 0.0 || result >= 1.0) {
-          return "Error budgets must be between 0 and 1";
+        if (!qubitType) {
+          return;
         }
-      };
 
-      const errorBudget = await window.showInputBox({
-        value: "0.001",
-        prompt: "Error budget",
-        validateInput: validateErrorBudget,
-      });
-
-      // abort if the user hits <Esc> during shots entry
-      if (errorBudget === undefined) {
-        return;
-      }
+        // Prompt for error budget (default to 0.001)
+        const validateErrorBudget = (input: string) => {
+          const result = parseFloat(input);
+          if (isNaN(result) || result <= 0.0 || result >= 1.0) {
+            return "Error budgets must be between 0 and 1";
+          }
+        };
 
-      let runName = await window.showInputBox({
-        title: "Friendly name for run",
-        value: `${program.programConfig.projectName}`,
-      });
-      if (!runName) {
-        return;
-      }
+        const errorBudget = await window.showInputBox({
+          value: "0.001",
+          prompt: "Error budget",
+          validateInput: validateErrorBudget,
+        });
 
-      const params = qubitType.map((item) => ({
-        ...item.params,
-        errorBudget: parseFloat(errorBudget),
-        estimateType: "frontier",
-      }));
-
-      log.info("RE params", params);
-
-      sendMessageToPanel({ panelType: "estimates" }, true, {
-        calculating: true,
-      });
-
-      const estimatePanel = getOrCreatePanel("estimates");
-      // Ensure the name is unique
-      if (estimatePanel.state[runName] !== undefined) {
-        let idx = 2;
-        for (;;) {
-          const newName = `${runName}-${idx}`;
-          if (estimatePanel.state[newName] === undefined) {
-            runName = newName;
-            break;
-          }
-          idx++;
+        // abort if the user hits <Esc> during shots entry
+        if (errorBudget === undefined) {
+          return;
         }
-      }
-      estimatePanel.state[runName] = true;
 
-      // Start the worker, run the code, and send the results to the webview
-      log.debug("Starting resource estimates worker.");
-      let timedOut = false;
+        let runName = await window.showInputBox({
+          title: "Friendly name for run",
+          value: `${program.programConfig.projectName}`,
+        });
+        if (!runName) {
+          return;
+        }
 
-      const worker = getCompilerWorker(compilerWorkerScriptPath);
-      const compilerTimeout = setTimeout(() => {
-        log.info("Compiler timeout. Terminating worker.");
-        timedOut = true;
-        worker.terminate();
-      }, compilerRunTimeoutMs);
+        const params = qubitType.map((item) => ({
+          ...item.params,
+          errorBudget: parseFloat(errorBudget),
+          estimateType: "frontier",
+        }));
 
-      try {
-        const start = performance.now();
-        sendTelemetryEvent(
-          EventType.ResourceEstimationStart,
-          { associationId },
-          {},
-        );
-        const estimatesStr = await worker.getEstimates(
-          program.programConfig,
-          JSON.stringify(params),
-        );
-        sendTelemetryEvent(
-          EventType.ResourceEstimationEnd,
-          { associationId },
-          { timeToCompleteMs: performance.now() - start },
-        );
-        log.debug("Estimates result", estimatesStr);
+        log.info("RE params", params);
 
-        // Should be an array of one ReData object returned
-        const estimates = JSON.parse(estimatesStr);
+        sendMessageToPanel({ panelType: "estimates" }, true, {
+          calculating: true,
+        });
 
-        for (const item of estimates) {
-          // if item doesn't have a status property, it's an error
-          if (!("status" in item) || item.status !== "success") {
-            log.error("Estimates error code: ", item.code);
-            log.error("Estimates error message: ", item.message);
-            throw item.message;
+        const estimatePanel = getOrCreatePanel("estimates");
+        // Ensure the name is unique
+        if (estimatePanel.state[runName] !== undefined) {
+          let idx = 2;
+          for (;;) {
+            const newName = `${runName}-${idx}`;
+            if (estimatePanel.state[newName] === undefined) {
+              runName = newName;
+              break;
+            }
+            idx++;
           }
         }
+        estimatePanel.state[runName] = true;
 
-        (estimates as Array<any>).forEach(
-          (item) => (item.jobParams.sharedRunName = runName),
-        );
-
-        clearTimeout(compilerTimeout);
+        // Start the worker, run the code, and send the results to the webview
+        log.debug("Starting resource estimates worker.");
+        let timedOut = false;
 
-        const message = {
-          calculating: false,
-          estimates,
-        };
-        sendMessageToPanel({ panelType: "estimates" }, true, message);
-      } catch (e: any) {
-        // Stop the 'calculating' animation
-        const message = {
-          calculating: false,
-          estimates: [],
-        };
-        sendMessageToPanel({ panelType: "estimates" }, false, message);
+        const worker = getCompilerWorker(compilerWorkerScriptPath);
+        const compilerTimeout = setTimeout(() => {
+          log.info("Compiler timeout. Terminating worker.");
+          timedOut = true;
+          worker.terminate();
+        }, compilerRunTimeoutMs);
+
+        try {
+          const start = performance.now();
+          sendTelemetryEvent(
+            EventType.ResourceEstimationStart,
+            { associationId },
+            {},
+          );
+          const estimatesStr = await worker.getEstimates(
+            program.programConfig,
+            expr ?? "",
+            JSON.stringify(params),
+          );
+          sendTelemetryEvent(
+            EventType.ResourceEstimationEnd,
+            { associationId },
+            { timeToCompleteMs: performance.now() - start },
+          );
+          log.debug("Estimates result", estimatesStr);
+
+          // Should be an array of one ReData object returned
+          const estimates = JSON.parse(estimatesStr);
+
+          for (const item of estimates) {
+            // if item doesn't have a status property, it's an error
+            if (!("status" in item) || item.status !== "success") {
+              log.error("Estimates error code: ", item.code);
+              log.error("Estimates error message: ", item.message);
+              throw item.message;
+            }
+          }
 
-        if (timedOut) {
-          // Show a VS Code popup that a timeout occurred
-          window.showErrorMessage(
-            "The resource estimation timed out. Please try again.",
+          (estimates as Array<any>).forEach(
+            (item) => (item.jobParams.sharedRunName = runName),
           );
-        } else {
-          log.error("getEstimates error: ", e.toString());
-          throw new Error("Estimating failed with error: " + e.toString());
-        }
-      } finally {
-        if (!timedOut) {
-          log.debug("Terminating resource estimates worker.");
-          worker.terminate();
+
+          clearTimeout(compilerTimeout);
+
+          const message = {
+            calculating: false,
+            estimates,
+          };
+          sendMessageToPanel({ panelType: "estimates" }, true, message);
+        } catch (e: any) {
+          // Stop the 'calculating' animation
+          const message = {
+            calculating: false,
+            estimates: [],
+          };
+          sendMessageToPanel({ panelType: "estimates" }, false, message);
+
+          if (timedOut) {
+            // Show a VS Code popup that a timeout occurred
+            window.showErrorMessage(
+              "The resource estimation timed out. Please try again.",
+            );
+          } else {
+            log.error("getEstimates error: ", e.toString());
+            throw new Error("Estimating failed with error: " + e.toString());
+          }
+        } finally {
+          if (!timedOut) {
+            log.debug("Terminating resource estimates worker.");
+            worker.terminate();
+          }
         }
-      }
-    }),
+      },
+    ),
   );
 
   context.subscriptions.push(
@@ -277,109 +282,116 @@ export function registerWebViewCommands(context: ExtensionContext) {
   );
 
   context.subscriptions.push(
-    commands.registerCommand(`${qsharpExtensionId}.showHistogram`, async () => {
-      clearCommandDiagnostics();
-
-      const associationId = getRandomGuid();
-      sendTelemetryEvent(EventType.TriggerHistogram, { associationId }, {});
-      function resultToLabel(result: string | VSDiagnostic): string {
-        if (typeof result !== "string") return "ERROR";
-        return result;
-      }
-
-      const program = await getActiveProgram();
-      if (!program.success) {
-        throw new Error(program.errorMsg);
-      }
-
-      const panelId = program.programConfig.projectName;
-
-      // Start the worker, run the code, and send the results to the webview
-      const worker = getCompilerWorker(compilerWorkerScriptPath);
-      const compilerTimeout = setTimeout(() => {
-        worker.terminate();
-      }, compilerRunTimeoutMs);
+    commands.registerCommand(
+      `${qsharpExtensionId}.showHistogram`,
+      async (resource?: vscode.Uri, expr?: string) => {
+        clearCommandDiagnostics();
+
+        const associationId = getRandomGuid();
+        sendTelemetryEvent(EventType.TriggerHistogram, { associationId }, {});
+        function resultToLabel(result: string | VSDiagnostic): string {
+          if (typeof result !== "string") return "ERROR";
+          return result;
+        }
 
-      try {
-        const validateShotsInput = (input: string) => {
-          const result = parseFloat(input);
-          if (isNaN(result) || Math.floor(result) !== result || result <= 0) {
-            return "Number of shots must be a positive integer";
-          }
-        };
+        const program = await getActiveProgram();
+        if (!program.success) {
+          throw new Error(program.errorMsg);
+        }
 
-        const numberOfShots =
-          (await window.showInputBox({
-            value: "100",
-            prompt: "Number of shots",
-            validateInput: validateShotsInput,
-          })) || "100";
+        const panelId = program.programConfig.projectName;
 
-        // abort if the user hits <Esc> during shots entry
-        if (numberOfShots === undefined) {
-          return;
-        }
+        // Start the worker, run the code, and send the results to the webview
+        const worker = getCompilerWorker(compilerWorkerScriptPath);
+        const compilerTimeout = setTimeout(() => {
+          worker.terminate();
+        }, compilerRunTimeoutMs);
+
+        try {
+          const validateShotsInput = (input: string) => {
+            const result = parseFloat(input);
+            if (isNaN(result) || Math.floor(result) !== result || result <= 0) {
+              return "Number of shots must be a positive integer";
+            }
+          };
 
-        sendMessageToPanel(
-          { panelType: "histogram", id: panelId },
-          true,
-          undefined,
-        );
+          const numberOfShots =
+            (await window.showInputBox({
+              value: "100",
+              prompt: "Number of shots",
+              validateInput: validateShotsInput,
+            })) || "100";
 
-        const evtTarget = new QscEventTarget(true);
-        evtTarget.addEventListener("uiResultsRefresh", () => {
-          const results = evtTarget.getResults();
-          const resultCount = evtTarget.resultCount();
-          const buckets = new Map();
-          for (let i = 0; i < resultCount; ++i) {
-            const key = results[i].result;
-            const strKey = resultToLabel(key);
-            const newValue = (buckets.get(strKey) || 0) + 1;
-            buckets.set(strKey, newValue);
+          // abort if the user hits <Esc> during shots entry
+          if (numberOfShots === undefined) {
+            return;
           }
-          const message = {
-            buckets: Array.from(buckets.entries()),
-            shotCount: resultCount,
-          };
+
           sendMessageToPanel(
             { panelType: "histogram", id: panelId },
-            false,
-            message,
+            true,
+            undefined,
           );
-        });
-        const start = performance.now();
-        sendTelemetryEvent(EventType.HistogramStart, { associationId }, {});
 
-        const noise = getPauliNoiseModel();
-        if (noise[0] != 0 || noise[1] != 0 || noise[2] != 0) {
-          sendTelemetryEvent(EventType.NoisySimulation, { associationId }, {});
+          const evtTarget = new QscEventTarget(true);
+          evtTarget.addEventListener("uiResultsRefresh", () => {
+            const results = evtTarget.getResults();
+            const resultCount = evtTarget.resultCount();
+            const buckets = new Map();
+            for (let i = 0; i < resultCount; ++i) {
+              const key = results[i].result;
+              const strKey = resultToLabel(key);
+              const newValue = (buckets.get(strKey) || 0) + 1;
+              buckets.set(strKey, newValue);
+            }
+            const message = {
+              buckets: Array.from(buckets.entries()),
+              shotCount: resultCount,
+            };
+            sendMessageToPanel(
+              { panelType: "histogram", id: panelId },
+              false,
+              message,
+            );
+          });
+          const start = performance.now();
+          sendTelemetryEvent(EventType.HistogramStart, { associationId }, {});
+
+          const noise = getPauliNoiseModel();
+          if (noise[0] != 0 || noise[1] != 0 || noise[2] != 0) {
+            sendTelemetryEvent(
+              EventType.NoisySimulation,
+              { associationId },
+              {},
+            );
+          }
+          await worker.runWithPauliNoise(
+            program.programConfig,
+            expr ?? "",
+            parseInt(numberOfShots),
+            noise,
+            evtTarget,
+          );
+          sendTelemetryEvent(
+            EventType.HistogramEnd,
+            { associationId },
+            { timeToCompleteMs: performance.now() - start },
+          );
+          clearTimeout(compilerTimeout);
+        } catch (e: any) {
+          log.error("Histogram error. ", e.toString());
+          throw new Error("Run failed. " + e.toString());
+        } finally {
+          worker.terminate();
         }
-        await worker.runWithPauliNoise(
-          program.programConfig,
-          "",
-          parseInt(numberOfShots),
-          noise,
-          evtTarget,
-        );
-        sendTelemetryEvent(
-          EventType.HistogramEnd,
-          { associationId },
-          { timeToCompleteMs: performance.now() - start },
-        );
-        clearTimeout(compilerTimeout);
-      } catch (e: any) {
-        log.error("Histogram error. ", e.toString());
-        throw new Error("Run failed. " + e.toString());
-      } finally {
-        worker.terminate();
-      }
-    }),
+      },
+    ),
   );
 
   context.subscriptions.push(
     commands.registerCommand(
       `${qsharpExtensionId}.showCircuit`,
-      async (operation?: IOperationInfo) => {
+      async (resource?: vscode.Uri, operation?: IOperationInfo) => {
         await showCircuitCommand(context.extensionUri, operation);
       },
     ),
diff --git a/wasm/src/language_service.rs b/wasm/src/language_service.rs
index 76ef3a96..8a2895a0 100644
--- a/wasm/src/language_service.rs
+++ b/wasm/src/language_service.rs
@@ -338,16 +338,27 @@ impl LanguageService {
             .map(|lens| {
                 let range = lens.range.into();
                 let (command, args) = match lens.command {
-                    qsls::protocol::CodeLensCommand::Histogram => ("histogram", None),
-                    qsls::protocol::CodeLensCommand::Debug => ("debug", None),
-                    qsls::protocol::CodeLensCommand::Run => ("run", None),
-                    qsls::protocol::CodeLensCommand::Estimate => ("estimate", None),
-                    qsls::protocol::CodeLensCommand::Circuit(args) => (
+                    qsls::protocol::CodeLensCommand::Histogram(expr) => {
+                        ("histogram", Some(CodeLensArg::Expr((None, expr))))
+                    }
+                    qsls::protocol::CodeLensCommand::Debug(expr) => {
+                        ("debug", Some(CodeLensArg::Expr((None, expr))))
+                    }
+                    qsls::protocol::CodeLensCommand::Run(expr) => {
+                        ("run", Some(CodeLensArg::Expr((None, expr))))
+                    }
+                    qsls::protocol::CodeLensCommand::Estimate(expr) => {
+                        ("estimate", Some(CodeLensArg::Expr((None, expr))))
+                    }
+                    qsls::protocol::CodeLensCommand::Circuit(op_info) => (
                         "circuit",
-                        args.map(|args| OperationInfo {
-                            operation: args.operation,
-                            total_num_qubits: args.total_num_qubits,
-                        }),
+                        Some(CodeLensArg::Operation((
+                            None,
+                            OperationInfo {
+                                operation: op_info.operation,
+                                total_num_qubits: op_info.total_num_qubits,
+                            },
+                        ))),
                     ),
                 };
                 CodeLens {
@@ -529,21 +540,25 @@ serializable_type! {
     }"#
 }
 
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(untagged)]
+pub enum CodeLensArg {
+    Expr((Option<String>, String)),
+    Operation((Option<String>, OperationInfo)),
+}
+
 serializable_type! {
     CodeLens,
     {
         range: Range,
         command: String,
         #[serde(skip_serializing_if = "Option::is_none")]
-        args: Option<OperationInfo>,
+        args: Option<CodeLensArg>,
     },
     r#"export type ICodeLens = {
         range: IRange;
-        command: "histogram" | "estimate" | "debug" | "run";
-    } | {
-        range: IRange;
-        command: "circuit";
-        args?: IOperationInfo
+        command: "histogram" | "estimate" | "debug" | "run" | "circuit";
+        args?: any[];
     }"#,
     ICodeLens
 }
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index a38a08ca..e162f8d4 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -82,12 +82,12 @@ pub(crate) fn get_qir_(
 }
 
 #[wasm_bindgen]
-pub fn get_estimates(program: ProgramConfig, params: &str) -> Result<String, String> {
-    let (source_map, capabilities, language_features, store, deps) = into_qsc_args(program, None)
-        .map_err(|mut e| {
-        // Wrap in `interpret::Error` to match the error type from `Interpreter::new` below
-        qsc::interpret::Error::from(e.pop().expect("expected at least one error")).to_string()
-    })?;
+pub fn get_estimates(program: ProgramConfig, expr: &str, params: &str) -> Result<String, String> {
+    let (source_map, capabilities, language_features, store, deps) =
+        into_qsc_args(program, Some(expr.into())).map_err(|mut e| {
+            // Wrap in `interpret::Error` to match the error type from `Interpreter::new` below
+            qsc::interpret::Error::from(e.pop().expect("expected at least one error")).to_string()
+        })?;
 
     let mut interpreter = interpret::Interpreter::new(
         source_map,
diff --git a/watch.mjs b/watch.mjs
index 27b37ebc..757cb686 100644
--- a/watch.mjs
+++ b/watch.mjs
@@ -17,8 +17,7 @@ Notes:
 - This builds the wasm module, npm package, VS Code extension, docs, katas, samples and runs the playground.
 - It does NOT build Python packages or native binaries (currently).
 - It does NOT build the Node.js wasm package (or run any of the node unit tests).
-- It builds debug binaries (whereas ./build.py builds for release).
-- Future updates could include supporting '--release'
+- It builds debug binaries unless the '--release' command line argument is provided.
 
 */
 
@@ -47,15 +46,36 @@ const samplesDir = join(thisDir, "samples");
 const isWin = process.platform === "win32";
 const npmCmd = isWin ? "npm.cmd" : "npm";
 
+const release = process.argv.includes("--release");
+
 function buildRust() {
-  console.log("Compiling the .wasm module with wasm-pack");
+  console.log(
+    "Compiling the .wasm module with wasm-pack " +
+      (release ? "(release)" : "(debug)"),
+  );
 
-  // This takes ~3-4 seconds on rebuild after some Rust changes. (Non-dev builds take ~15-20 seconds)
+  // Dev build takes ~3-4 seconds on rebuild after some Rust changes. (Non-dev builds take ~15-20 seconds)
   // Build only web and not node targets to half time.
-  const buildDir = join(thisDir, "target", "wasm32", "debug", "web");
+  const buildDir = join(
+    thisDir,
+    "target",
+    "wasm32",
+    release ? "release" : "debug",
+    "web",
+  );
   const result = spawnSync(
     "wasm-pack",
-    ["build", "--dev", "--no-pack", "--target", "web", "--out-dir", buildDir],
+    release
+      ? ["build", "--no-pack", "--target", "web", "--out-dir", buildDir]
+      : [
+          "build",
+          "--dev",
+          "--no-pack",
+          "--target",
+          "web",
+          "--out-dir",
+          buildDir,
+        ],
     { cwd: wasmDir, shell: true },
   );
   console.log("wasm-pack done! ", result.stderr.toString());


Test patch:
diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs
index f3188cba..53bb19d4 100644
--- a/compiler/qsc/src/interpret/circuit_tests.rs
+++ b/compiler/qsc/src/interpret/circuit_tests.rs
@@ -490,18 +490,25 @@ fn custom_intrinsic_mixed_args() {
         .circuit(CircuitEntryPoint::EntryPoint, false)
         .expect("circuit generation should succeed");
 
-    // This is one gate that spans ten target wires, even though the
-    // text visualization doesn't convey that clearly.
     expect![[r"
         q_0    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_1    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_2    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_3    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_4    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_5    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_6    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_7    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_8    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+                                                         ┆
         q_9    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
     "]]
     .assert_eq(&circ.to_string());
@@ -887,6 +894,113 @@ fn operation_with_long_gates_properly_aligned() {
     .assert_eq(&circ.to_string());
 }
 
+#[test]
+fn operation_with_subsequent_qubits_gets_horizontal_lines() {
+    let mut interpreter = interpreter(
+        r"
+            namespace Test {
+                import Std.Measurement.*;
+
+                @EntryPoint()
+                operation Main() : Unit {
+                    use q0 = Qubit();
+                    use q1 = Qubit();
+                    Rxx(1.0, q0, q1);
+
+                    use q2 = Qubit();
+                    use q3 = Qubit();
+                    Rxx(1.0, q2, q3);
+                }
+            }
+        ",
+        Profile::Unrestricted,
+    );
+
+    let circ = interpreter
+        .circuit(CircuitEntryPoint::EntryPoint, false)
+        .expect("circuit generation should succeed");
+
+    expect![[r#"
+        q_0    ─ rxx(1.0000) ─
+                      ┆
+        q_1    ─ rxx(1.0000) ─
+        q_2    ─ rxx(1.0000) ─
+                      ┆
+        q_3    ─ rxx(1.0000) ─
+    "#]]
+    .assert_eq(&circ.to_string());
+}
+
+#[test]
+fn operation_with_subsequent_qubits_no_double_rows() {
+    let mut interpreter = interpreter(
+        r"
+            namespace Test {
+                import Std.Measurement.*;
+
+                @EntryPoint()
+                operation Main() : Unit {
+                    use q0 = Qubit();
+                    use q1 = Qubit();
+                    Rxx(1.0, q0, q1);
+                    Rxx(1.0, q0, q1);
+                }
+            }
+        ",
+        Profile::Unrestricted,
+    );
+
+    let circ = interpreter
+        .circuit(CircuitEntryPoint::EntryPoint, false)
+        .expect("circuit generation should succeed");
+
+    expect![[r#"
+        q_0    ─ rxx(1.0000) ── rxx(1.0000) ─
+                      ┆              ┆
+        q_1    ─ rxx(1.0000) ── rxx(1.0000) ─
+    "#]]
+    .assert_eq(&circ.to_string());
+}
+
+#[test]
+fn operation_with_subsequent_qubits_no_added_rows() {
+    let mut interpreter = interpreter(
+        r"
+            namespace Test {
+                import Std.Measurement.*;
+
+                @EntryPoint()
+                operation Main() : Result[] {
+                    use q0 = Qubit();
+                    use q1 = Qubit();
+                    Rxx(1.0, q0, q1);
+
+                    use q2 = Qubit();
+                    use q3 = Qubit();
+                    Rxx(1.0, q2, q3);
+
+                    [M(q0), M(q2)]
+                }
+            }
+        ",
+        Profile::Unrestricted,
+    );
+
+    let circ = interpreter
+        .circuit(CircuitEntryPoint::EntryPoint, false)
+        .expect("circuit generation should succeed");
+
+    expect![[r#"
+        q_0    ─ rxx(1.0000) ─── M ──
+                      ┆          ╘═══
+        q_1    ─ rxx(1.0000) ────────
+        q_2    ─ rxx(1.0000) ─── M ──
+                      ┆          ╘═══
+        q_3    ─ rxx(1.0000) ────────
+    "#]]
+    .assert_eq(&circ.to_string());
+}
+
 /// Tests that invoke circuit generation throught the debugger.
 mod debugger_stepping {
     use super::Debugger;
diff --git a/compiler/qsc_circuit/src/operations/tests.rs b/compiler/qsc_circuit/src/operations/tests.rs
index d782f404..c57425b0 100644
--- a/compiler/qsc_circuit/src/operations/tests.rs
+++ b/compiler/qsc_circuit/src/operations/tests.rs
@@ -62,8 +62,8 @@ fn no_params() {
     );
     let expr = entry_expr_for_qubit_operation(&item, FunctorApp::default(), &operation);
     expect![[r#"
-        Err(
-            NoQubitParameters,
+        Ok(
+            "{\n            use qs = Qubit[0];\n            (Test.Test)();\n            let r: Result[] = [];\n            r\n        }",
         )
     "#]]
     .assert_debug_eq(&expr);
diff --git a/compiler/qsc_frontend/src/lower/tests.rs b/compiler/qsc_frontend/src/lower/tests.rs
index f69873a7..02d3e2c0 100644
--- a/compiler/qsc_frontend/src/lower/tests.rs
+++ b/compiler/qsc_frontend/src/lower/tests.rs
@@ -2380,6 +2380,90 @@ fn lambda_with_invalid_free_variable() {
     );
 }
 
+#[test]
+fn lambda_with_explicit_return_should_have_non_unit_output_type() {
+    check_hir(
+        "function Foo(): Unit { let f = i -> return i + 1;}",
+        &expect![[r#"
+            Package:
+                Item 0 [0-50] (Public):
+                    Namespace (Ident 21 [0-50] "test"): Item 1
+                Item 1 [0-50] (Internal):
+                    Parent: 0
+                    Callable 0 [0-50] (function):
+                        name: Ident 1 [9-12] "Foo"
+                        input: Pat 2 [12-14] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-50]: Impl:
+                            Block 4 [21-50] [Type Unit]:
+                                Stmt 5 [23-49]: Local (Immutable):
+                                    Pat 6 [27-28] [Type (Int -> Int)]: Bind: Ident 7 [27-28] "f"
+                                    Expr 8 [31-48] [Type (Int -> Int)]: Closure([], 2)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>
+                Item 2 [31-48] (Internal):
+                    Parent: 1
+                    Callable 16 [31-48] (function):
+                        name: Ident 17 [31-48] "<lambda>"
+                        input: Pat 15 [31-48] [Type (Int,)]: Tuple:
+                            Pat 9 [31-32] [Type Int]: Bind: Ident 10 [31-32] "i"
+                        output: Int
+                        functors: empty set
+                        body: SpecDecl 18 [36-48]: Impl:
+                            Block 19 [36-48] [Type Int]:
+                                Stmt 20 [36-48]: Expr: Expr 11 [36-48] [Type Int]: Return: Expr 12 [43-48] [Type Int]: BinOp (Add):
+                                    Expr 13 [43-44] [Type Int]: Var: Local 10
+                                    Expr 14 [47-48] [Type Int]: Lit: Int(1)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn lambda_with_implicit_return_should_have_non_unit_output_type() {
+    check_hir(
+        "function Foo(): Unit { let f = i -> i + 1}",
+        &expect![[r#"
+            Package:
+                Item 0 [0-42] (Public):
+                    Namespace (Ident 20 [0-42] "test"): Item 1
+                Item 1 [0-42] (Internal):
+                    Parent: 0
+                    Callable 0 [0-42] (function):
+                        name: Ident 1 [9-12] "Foo"
+                        input: Pat 2 [12-14] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-42]: Impl:
+                            Block 4 [21-42] [Type Unit]:
+                                Stmt 5 [23-41]: Local (Immutable):
+                                    Pat 6 [27-28] [Type (Int -> Int)]: Bind: Ident 7 [27-28] "f"
+                                    Expr 8 [31-41] [Type (Int -> Int)]: Closure([], 2)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>
+                Item 2 [31-41] (Internal):
+                    Parent: 1
+                    Callable 15 [31-41] (function):
+                        name: Ident 16 [31-41] "<lambda>"
+                        input: Pat 14 [31-41] [Type (Int,)]: Tuple:
+                            Pat 9 [31-32] [Type Int]: Bind: Ident 10 [31-32] "i"
+                        output: Int
+                        functors: empty set
+                        body: SpecDecl 17 [36-41]: Impl:
+                            Block 18 [36-41] [Type Int]:
+                                Stmt 19 [36-41]: Expr: Expr 11 [36-41] [Type Int]: BinOp (Add):
+                                    Expr 12 [36-37] [Type Int]: Var: Local 10
+                                    Expr 13 [40-41] [Type Int]: Lit: Int(1)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
 #[test]
 fn duplicate_commas_in_tydef() {
     check_hir(
diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs
index 1933bdc0..c8f02411 100644
--- a/compiler/qsc_frontend/src/typeck/tests.rs
+++ b/compiler/qsc_frontend/src/typeck/tests.rs
@@ -3164,8 +3164,8 @@ fn lambda_inner_return() {
             #12 54-55 "f" : (Unit -> Int)
             #14 58-98 "() -> {\n            return 42;\n        }" : (Unit -> Int)
             #15 58-60 "()" : Unit
-            #16 64-98 "{\n            return 42;\n        }" : Unit
-            #17 64-98 "{\n            return 42;\n        }" : Unit
+            #16 64-98 "{\n            return 42;\n        }" : Int
+            #17 64-98 "{\n            return 42;\n        }" : Int
             #19 78-87 "return 42" : Unit
             #20 85-87 "42" : Int
             #22 112-113 "r" : Int
@@ -3197,8 +3197,8 @@ fn lambda_inner_return_without_call_ambiguous() {
             #15 58-64 "(a, b)" : (?2, ?2)
             #16 59-60 "a" : ?2
             #18 62-63 "b" : ?2
-            #20 68-105 "{\n            return a + b;\n        }" : Unit
-            #21 68-105 "{\n            return a + b;\n        }" : Unit
+            #20 68-105 "{\n            return a + b;\n        }" : ?2
+            #21 68-105 "{\n            return a + b;\n        }" : ?2
             #23 82-94 "return a + b" : Unit
             #24 89-94 "a + b" : ?2
             #25 89-90 "a" : ?2
@@ -3224,17 +3224,17 @@ fn lambda_implicit_return_without_call_ambiguous() {
         &expect![[r#"
             #6 30-32 "()" : Unit
             #10 40-104 "{\n        let f = (a, b) -> {\n            a + b\n        };\n    }" : Unit
-            #12 54-55 "f" : ((?2, ?2) -> ?2)
-            #14 58-97 "(a, b) -> {\n            a + b\n        }" : ((?2, ?2) -> ?2)
-            #15 58-64 "(a, b)" : (?2, ?2)
-            #16 59-60 "a" : ?2
-            #18 62-63 "b" : ?2
-            #20 68-97 "{\n            a + b\n        }" : ?2
-            #21 68-97 "{\n            a + b\n        }" : ?2
-            #23 82-87 "a + b" : ?2
-            #24 82-83 "a" : ?2
-            #27 86-87 "b" : ?2
-            Error(Type(Error(AmbiguousTy(Span { lo: 62, hi: 63 }))))
+            #12 54-55 "f" : ((?3, ?3) -> ?3)
+            #14 58-97 "(a, b) -> {\n            a + b\n        }" : ((?3, ?3) -> ?3)
+            #15 58-64 "(a, b)" : (?3, ?3)
+            #16 59-60 "a" : ?3
+            #18 62-63 "b" : ?3
+            #20 68-97 "{\n            a + b\n        }" : ?3
+            #21 68-97 "{\n            a + b\n        }" : ?3
+            #23 82-87 "a + b" : ?3
+            #24 82-83 "a" : ?3
+            #27 86-87 "b" : ?3
+            Error(Type(Error(AmbiguousTy(Span { lo: 68, hi: 97 }))))
         "#]],
     );
 }
diff --git a/compiler/qsc_rca/src/tests/lambdas.rs b/compiler/qsc_rca/src/tests/lambdas.rs
index 35baefde..17ffc572 100644
--- a/compiler/qsc_rca/src/tests/lambdas.rs
+++ b/compiler/qsc_rca/src/tests/lambdas.rs
@@ -217,3 +217,41 @@ fn check_rca_for_operation_lambda_two_parameters_with_controls() {
                 dynamic_param_applications: <empty>"#]],
     );
 }
+
+#[test]
+fn check_rca_for_function_lambda_using_array_slicing_with_explicit_return() {
+    let mut compilation_context = CompilationContext::default();
+    compilation_context.update(
+        r#"
+        let lambda = (a, n) -> { return a[...n - 1] };
+        lambda([1, 2, 3], 2)
+        "#,
+    );
+    let package_store_compute_properties = compilation_context.get_compute_properties();
+    check_last_statement_compute_properties(
+        package_store_compute_properties,
+        &expect![[r#"
+            ApplicationsGeneratorSet:
+                inherent: Classical
+                dynamic_param_applications: <empty>"#]],
+    );
+}
+
+#[test]
+fn check_rca_for_function_lambda_using_array_slicing_with_implicit_return() {
+    let mut compilation_context = CompilationContext::default();
+    compilation_context.update(
+        r#"
+        let lambda = (a, n) -> { a[...n - 1] };
+        lambda([1, 2, 3], 2)
+        "#,
+    );
+    let package_store_compute_properties = compilation_context.get_compute_properties();
+    check_last_statement_compute_properties(
+        package_store_compute_properties,
+        &expect![[r#"
+            ApplicationsGeneratorSet:
+                inherent: Classical
+                dynamic_param_applications: <empty>"#]],
+    );
+}
diff --git a/language_service/src/code_lens/tests.rs b/language_service/src/code_lens/tests.rs
index a008b796..9c5fa5db 100644
--- a/language_service/src/code_lens/tests.rs
+++ b/language_service/src/code_lens/tests.rs
@@ -58,12 +58,23 @@ fn one_entrypoint() {
                 (
                     0,
                     [
-                        Run,
-                        Histogram,
-                        Estimate,
-                        Debug,
+                        Run(
+                            "Test.Test()",
+                        ),
+                        Histogram(
+                            "Test.Test()",
+                        ),
+                        Estimate(
+                            "Test.Test()",
+                        ),
+                        Debug(
+                            "Test.Test()",
+                        ),
                         Circuit(
-                            None,
+                            OperationInfo {
+                                operation: "Test.Test",
+                                total_num_qubits: 0,
+                            },
                         ),
                     ],
                 ),
@@ -78,15 +89,62 @@ fn two_entrypoints() {
         r#"
         namespace Test {
             @EntryPoint()
-            operation Main() : Unit{
-            }
+            ◉operation Main() : Unit{
+            }◉
 
             @EntryPoint()
-            operation Foo() : Unit{
-            }
+            ◉operation Foo() : Unit{
+            }◉
         }"#,
         &expect![[r#"
-            []
+            [
+                (
+                    0,
+                    [
+                        Run(
+                            "Test.Main()",
+                        ),
+                        Histogram(
+                            "Test.Main()",
+                        ),
+                        Estimate(
+                            "Test.Main()",
+                        ),
+                        Debug(
+                            "Test.Main()",
+                        ),
+                        Circuit(
+                            OperationInfo {
+                                operation: "Test.Main",
+                                total_num_qubits: 0,
+                            },
+                        ),
+                    ],
+                ),
+                (
+                    1,
+                    [
+                        Run(
+                            "Test.Foo()",
+                        ),
+                        Histogram(
+                            "Test.Foo()",
+                        ),
+                        Estimate(
+                            "Test.Foo()",
+                        ),
+                        Debug(
+                            "Test.Foo()",
+                        ),
+                        Circuit(
+                            OperationInfo {
+                                operation: "Test.Foo",
+                                total_num_qubits: 0,
+                            },
+                        ),
+                    ],
+                ),
+            ]
         "#]],
     );
 }
@@ -99,20 +157,54 @@ fn main_function() {
             ◉operation Main() : Unit {
             }◉
 
-            operation Foo() : Unit{
-            }
+            ◉operation Foo() : Unit{
+            }◉
         }"#,
         &expect![[r#"
             [
                 (
                     0,
                     [
-                        Run,
-                        Histogram,
-                        Estimate,
-                        Debug,
+                        Run(
+                            "Test.Main()",
+                        ),
+                        Histogram(
+                            "Test.Main()",
+                        ),
+                        Estimate(
+                            "Test.Main()",
+                        ),
+                        Debug(
+                            "Test.Main()",
+                        ),
+                        Circuit(
+                            OperationInfo {
+                                operation: "Test.Main",
+                                total_num_qubits: 0,
+                            },
+                        ),
+                    ],
+                ),
+                (
+                    1,
+                    [
+                        Run(
+                            "Test.Foo()",
+                        ),
+                        Histogram(
+                            "Test.Foo()",
+                        ),
+                        Estimate(
+                            "Test.Foo()",
+                        ),
+                        Debug(
+                            "Test.Foo()",
+                        ),
                         Circuit(
-                            None,
+                            OperationInfo {
+                                operation: "Test.Foo",
+                                total_num_qubits: 0,
+                            },
                         ),
                     ],
                 ),
@@ -153,12 +245,10 @@ fn qubit_operation_circuit() {
                     0,
                     [
                         Circuit(
-                            Some(
-                                OperationInfo {
-                                    operation: "Test.Foo",
-                                    total_num_qubits: 1,
-                                },
-                            ),
+                            OperationInfo {
+                                operation: "Test.Foo",
+                                total_num_qubits: 1,
+                            },
                         ),
                     ],
                 ),
@@ -181,12 +271,10 @@ fn qubit_arrays_operation_circuit() {
                     0,
                     [
                         Circuit(
-                            Some(
-                                OperationInfo {
-                                    operation: "Test.Foo",
-                                    total_num_qubits: 7,
-                                },
-                            ),
+                            OperationInfo {
+                                operation: "Test.Foo",
+                                total_num_qubits: 7,
+                            },
                         ),
                     ],
                 ),
diff --git a/library/chemistry/src/Tests.qs b/library/chemistry/src/Tests.qs
index 2b44b292..2a6cfdf2 100644
--- a/library/chemistry/src/Tests.qs
+++ b/library/chemistry/src/Tests.qs
@@ -2,7 +2,6 @@ import Std.StatePreparation.ApproximatelyPreparePureStateCP;
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-import JordanWigner.JordanWignerOptimizedBlockEncoding.PrepareUniformSuperposition;
 import JordanWigner.JordanWignerClusterOperatorEvolutionSet.JordanWignerClusterOperatorPQRSTermSigns;
 import JordanWigner.OptimizedBEOperator.OptimizedBEXY;
 import JordanWigner.OptimizedBEOperator.SelectZ;
@@ -23,20 +22,6 @@ import Std.Math.Lg;
 import Std.Arrays.Reversed;
 import Std.Math.Sqrt;
 
-@Config(Unrestricted)
-@Test()
-operation PrepareUniformSuperpositionTest() : Unit {
-    let NBits = 4;
-    use qs = Qubit[NBits];
-    for NStates in 1..2^NBits-1 {
-        PrepareUniformSuperposition(NStates, qs);
-        let t = Std.Math.Sqrt(1.0 / IntAsDouble(NStates));
-        Adjoint Std.StatePreparation.PreparePureStateD(Repeated(t, NStates), Reversed(qs));
-        Fact(CheckAllZero(qs), "All Z");
-        ResetAll(qs);
-    }
-}
-
 @Config(Unrestricted)
 @Test()
 operation PrepareSparseMultiConfigurationalState0Test() : Unit {
diff --git a/library/fixed_point/src/Tests.qs b/library/fixed_point/src/Tests.qs
index 29568bea..fa7e1466 100644
--- a/library/fixed_point/src/Tests.qs
+++ b/library/fixed_point/src/Tests.qs
@@ -3,12 +3,12 @@
 
 import Init.PrepareFxP;
 import Types.FixedPoint;
-import Measurement.MeasureFxP;
 import Std.Diagnostics.Fact;
 import Std.Convert.IntAsDouble;
 import Std.Math.AbsD;
 import Operations.*;
 
+@Config(Unrestricted)
 @Test()
 operation FxpMeasurementTest() : Unit {
     for numQubits in 3..12 {
@@ -29,7 +29,9 @@ operation FxpMeasurementTest() : Unit {
     }
 }
 
+@Config(Unrestricted)
 operation TestConstantMeasurement(constant : Double, registerWidth : Int, integerWidth : Int, epsilon : Double) : Unit {
+    import Measurement.MeasureFxP;
     use register = Qubit[registerWidth];
     let newFxp = new FixedPoint { IntegerBits = integerWidth, Register = register };
     PrepareFxP(constant, newFxp);
@@ -39,6 +41,7 @@ operation TestConstantMeasurement(constant : Double, registerWidth : Int, intege
     ResetAll(register);
 }
 
+@Config(Unrestricted)
 @Test()
 operation FxpOperationTests() : Unit {
     for i in 0..10 {
@@ -51,8 +54,9 @@ operation FxpOperationTests() : Unit {
         TestSquare(constant1);
     }
 }
-
+@Config(Unrestricted)
 operation TestSquare(a : Double) : Unit {
+    import Measurement.MeasureFxP;
     Message($"Testing Square({a})");
     use resultRegister = Qubit[30];
     let resultFxp = new FixedPoint { IntegerBits = 8, Register = resultRegister };
@@ -69,8 +73,10 @@ operation TestSquare(a : Double) : Unit {
     ResetAll(aRegister);
 }
 
+@Config(Unrestricted)
 // assume the second register that `op` takes is the result register
 operation TestOperation(a : Double, b : Double, op : (FixedPoint, FixedPoint) => (), reference : (Double, Double) -> Double, name : String) : Unit {
+    import Measurement.MeasureFxP;
     Message($"Testing {name}({a}, {b})");
     use register1 = Qubit[20];
     let aFxp = new FixedPoint { IntegerBits = 8, Register = register1 };
@@ -89,8 +95,11 @@ operation TestOperation(a : Double, b : Double, op : (FixedPoint, FixedPoint) =>
     ResetAll(register1 + register2);
 }
 
+@Config(Unrestricted)
 // assume the third register that `op` takes is the result register
 operation TestOperation3(a : Double, b : Double, op : (FixedPoint, FixedPoint, FixedPoint) => (), reference : (Double, Double) -> Double, name : String) : Unit {
+    import Measurement.MeasureFxP;
+
     Message($"Testing {name}({a}, {b})");
     use register1 = Qubit[24];
     let aFxp = new FixedPoint { IntegerBits = 8, Register = register1 };
diff --git a/library/src/tests/resources/src/state_preparation.qs b/library/src/tests/resources/src/state_preparation.qs
index 9b2e30d0..3e4ae311 100644
--- a/library/src/tests/resources/src/state_preparation.qs
+++ b/library/src/tests/resources/src/state_preparation.qs
@@ -106,4 +106,22 @@ namespace Test {
         }
     }
 
+    operation TestPrepareUniformSuperposition(nStates : Int) : Unit {
+        use qs = Qubit[10];
+        PrepareUniformSuperposition(nStates, qs);
+        DumpMachine();
+        ResetAll(qs);
+    }
+
+    operation TestPrepareUniformSuperpositionExhaustive() : Unit {
+        let NBits = 4;
+        for NStates in 1..2^NBits-1 {
+            use qs = Qubit[NBits];
+            PrepareUniformSuperposition(NStates, qs);
+            let t = Std.Math.Sqrt(1.0 / IntAsDouble(NStates));
+            Adjoint Std.StatePreparation.PreparePureStateD(Repeated(t, NStates), Reversed(qs));
+            Fact(CheckAllZero(qs), "All qubits must be in zero state.");
+        }
+    }
+
 }
diff --git a/library/src/tests/state_preparation.rs b/library/src/tests/state_preparation.rs
index c6575eca..c48e2ace 100644
--- a/library/src/tests/state_preparation.rs
+++ b/library/src/tests/state_preparation.rs
@@ -2,6 +2,7 @@
 // Licensed under the MIT License.
 
 use super::test_expression;
+use super::test_expression_fails;
 use super::test_expression_with_lib;
 use expect_test::expect;
 use qsc::interpret::Value;
@@ -254,3 +255,55 @@ fn check_preparation_doc_sample() {
     "#]]
     .assert_eq(&out);
 }
+
+#[test]
+fn check_uniform_superposition_preparation() {
+    let out = test_expression_with_lib(
+        "Test.TestPrepareUniformSuperposition(5)",
+        STATE_PREPARATION_TEST_LIB,
+        &Value::Tuple(vec![].into()),
+    );
+
+    expect![[r#"
+        STATE:
+        |0000000000⟩: 0.4472+0.0000𝑖
+        |0010000000⟩: 0.4472+0.0000𝑖
+        |0100000000⟩: 0.4472+0.0000𝑖
+        |1000000000⟩: 0.4472+0.0000𝑖
+        |1100000000⟩: 0.4472+0.0000𝑖
+    "#]]
+    .assert_eq(&out);
+}
+
+#[test]
+fn check_uniform_superposition_preparation_exhaustive() {
+    let _ = test_expression_with_lib(
+        "Test.TestPrepareUniformSuperpositionExhaustive()",
+        STATE_PREPARATION_TEST_LIB,
+        &Value::Tuple(vec![].into()),
+    );
+}
+
+#[test]
+fn check_uniform_superposition_short_array() {
+    let out = test_expression_fails(
+        "{
+            use qs=Qubit[2];
+            Std.StatePreparation.PrepareUniformSuperposition(5, qs);
+        }",
+    );
+
+    expect!["program failed: Qubit register is too short to prepare 5 states."].assert_eq(&out);
+}
+
+#[test]
+fn check_uniform_superposition_invalid_state_count() {
+    let out = test_expression_fails(
+        "{
+            use qs=Qubit[2];
+            Std.StatePreparation.PrepareUniformSuperposition(0, qs);
+        }",
+    );
+
+    expect!["program failed: Number of basis states must be positive."].assert_eq(&out);
+}
diff --git a/library/src/tests/table_lookup.rs b/library/src/tests/table_lookup.rs
index 69efe511..d9dd89dc 100644
--- a/library/src/tests/table_lookup.rs
+++ b/library/src/tests/table_lookup.rs
@@ -8,6 +8,15 @@ use qsc::interpret::Value;
 
 const SELECT_TEST_LIB: &str = include_str!("resources/src/select.qs");
 
+#[test]
+fn check_select_exhaustive_bitwidth_0() {
+    test_expression_with_lib(
+        "Test.TestSelect(0, 10)",
+        SELECT_TEST_LIB,
+        &Value::Tuple(vec![].into()),
+    );
+}
+
 #[test]
 fn check_select_exhaustive_bitwidth_1() {
     test_expression_with_lib(
diff --git a/vscode/test/suites/debugger/debugger.test.ts b/vscode/test/suites/debugger/debugger.test.ts
index 1bf382ed..8ba06cd8 100644
--- a/vscode/test/suites/debugger/debugger.test.ts
+++ b/vscode/test/suites/debugger/debugger.test.ts
@@ -100,38 +100,35 @@ suite("Q# Debugger Tests", function suite() {
     ]);
   });
 
-  // Scenario broken with VS Code change https://github.com/microsoft/vscode/issues/197377
-  // Fix the extension and/or re-enable test when the VS Code issue reaches a resolution.
-  //
-  // test("Launch with launch.json configuration - file substitution", async () => {
-  //   await vscode.window.showTextDocument(fooUri);
-
-  //   // ${file} will expand to the filesystem path of the currently opened file
-  //   await vscode.debug.startDebugging(workspaceFolder, {
-  //     name: "Launch foo.qs",
-  //     type: "qsharp",
-  //     request: "launch",
-  //     program: "${file}",
-  //   });
-
-  //   await waitUntilPaused([
-  //     {
-  //       id: 0,
-  //       source: {
-  //         name: "foo.qs",
-  //         path: "vscode-test-web://mount/src/foo.qs",
-  //         sourceReference: 0,
-  //         adapterData: "qsharp-adapter-data",
-  //       },
-  //       line: 5,
-  //       column: 9,
-  //       name: "Foo ",
-  //       endLine: 5,
-  //       endColumn: 15,
-  //     },
-  //     { id: 0, line: 0, column: 0, name: "entry", source: undefined },
-  //   ]);
-  // });
+  test("Launch with launch.json configuration - file substitution", async () => {
+    await vscode.window.showTextDocument(fooUri);
+
+    // ${file} will expand to the filesystem path of the currently opened file
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${file}",
+    });
+
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+  });
 
   test("Run until completion", async () => {
     // launch debugger
diff --git a/vscode/test/suites/language-service/language-service.test.ts b/vscode/test/suites/language-service/language-service.test.ts
index cffadfe8..7eac03e5 100644
--- a/vscode/test/suites/language-service/language-service.test.ts
+++ b/vscode/test/suites/language-service/language-service.test.ts
@@ -227,7 +227,7 @@ suite("Q# Language Service Tests", function suite() {
       doc.uri,
     )) as vscode.CodeLens[];
 
-    assert.lengthOf(actualCodeLenses, 5);
+    assert.lengthOf(actualCodeLenses, 4);
 
     for (const lens of actualCodeLenses) {
       assert.include(doc.getText(lens.range), "function Test()");