Repository: microsoft/qsharp
Pull request #1031 (2024-01-17 22:39:00)

Update Rust dependencies


Patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 6db27755..7f88f3f6 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -19,6 +19,7 @@
 				"esbenp.prettier-vscode",
 				"ms-python.python",
 				"ms-python.black-formatter",
+				"ms-toolsai.jupyter",
 				"rust-lang.rust-analyzer",
 				"vadimcn.vscode-lldb",
 				"swellaby.workspace-config-plus"
diff --git a/Cargo.lock b/Cargo.lock
index a7b215d3..89545a52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -34,9 +34,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.6.4"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -54,27 +54,27 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
 dependencies = [
  "windows-sys",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
 dependencies = [
  "anstyle",
  "windows-sys",
@@ -82,15 +82,15 @@ dependencies = [
 
 [[package]]
 name = "arbitrary"
-version = "1.3.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2e1373abdaa212b704512ec2bd8b26bd0b7d5c3f70117411a5d9a451383c859"
+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
 
 [[package]]
 name = "async-trait"
-version = "0.1.74"
+version = "0.1.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -135,9 +135,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.1"
+version = "2.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 
 [[package]]
 name = "bumpalo"
@@ -196,9 +196,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.6"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -206,9 +206,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.6"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
 dependencies = [
  "anstream",
  "anstyle",
@@ -218,9 +218,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.4.2"
+version = "4.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -230,9 +230,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.5.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
 
 [[package]]
 name = "colorchoice"
@@ -288,18 +288,18 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
 [[package]]
 name = "enum-iterator"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689"
+checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94"
 dependencies = [
  "enum-iterator-derive",
 ]
 
 [[package]]
 name = "enum-iterator-derive"
-version = "1.2.1"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
+checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -308,9 +308,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.10.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
 dependencies = [
  "humantime",
  "is-terminal",
@@ -321,9 +321,9 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
  "windows-sys",
@@ -347,9 +347,9 @@ checksum = "4f4cdac9e4065d7c48e30770f8665b8cef9a3a73a63a4056a33a5f395bc7cf75"
 
 [[package]]
 name = "futures"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -362,9 +362,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -372,15 +372,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -389,15 +389,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -406,21 +406,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
 
 [[package]]
 name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
 name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -444,9 +444,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.10"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -457,9 +457,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.28.0"
+version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
 [[package]]
 name = "half"
@@ -475,9 +475,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
 
 [[package]]
 name = "humantime"
@@ -499,9 +499,9 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
 
 [[package]]
 name = "is-terminal"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
 dependencies = [
  "hermit-abi",
  "rustix",
@@ -525,9 +525,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "jobserver"
@@ -540,9 +540,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.64"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -556,9 +556,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.149"
+version = "0.2.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
 
 [[package]]
 name = "libfuzzer-sys"
@@ -577,11 +577,20 @@ version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
 
+[[package]]
+name = "library"
+version = "0.0.0"
+dependencies = [
+ "indoc",
+ "num-bigint",
+ "qsc",
+]
+
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.10"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
@@ -611,9 +620,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "memoffset"
@@ -719,18 +728,18 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "oorandom"
@@ -764,7 +773,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -797,18 +806,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "pyo3"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b"
+checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0"
 dependencies = [
  "cfg-if",
  "indoc",
@@ -824,9 +833,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5"
+checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be"
 dependencies = [
  "once_cell",
  "python3-dll-a",
@@ -835,9 +844,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b"
+checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -845,9 +854,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b"
+checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
@@ -857,9 +866,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424"
+checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -969,10 +978,11 @@ dependencies = [
 name = "qsc_frontend"
 version = "0.0.0"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "enum-iterator",
  "expect-test",
  "indoc",
+ "library",
  "miette",
  "num-bigint",
  "num-traits",
@@ -1099,7 +1109,7 @@ dependencies = [
 [[package]]
 name = "quantum-sparse-sim"
 version = "0.5.0"
-source = "git+https://github.com/qir-alliance/qir-runner?rev=f000e0066f56338595be37092cd0498f72ab10a2#f000e0066f56338595be37092cd0498f72ab10a2"
+source = "git+https://github.com/qir-alliance/qir-runner?rev=e7de80bf06dcaf69367576ec31f901c0496a9832#e7de80bf06dcaf69367576ec31f901c0496a9832"
 dependencies = [
  "ndarray",
  "num-bigint",
@@ -1111,9 +1121,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -1238,11 +1248,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustix"
-version = "0.38.20"
+version = "0.38.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1251,9 +1261,9 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
 
 [[package]]
 name = "same-file"
@@ -1272,18 +1282,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.189"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde-wasm-bindgen"
-version = "0.6.0"
+version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
+checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132"
 dependencies = [
  "js-sys",
  "serde",
@@ -1292,9 +1302,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.189"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1303,9 +1313,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.107"
+version = "1.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
 dependencies = [
  "itoa",
  "ryu",
@@ -1323,9 +1333,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.1"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8"
 
 [[package]]
 name = "smawk"
@@ -1335,22 +1345,13 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
 
 [[package]]
 name = "special"
-version = "0.10.2"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322ed043a063dd3cabb4b53b2a37bce115854d94df19acf766d9dacaff218c5a"
+checksum = "b89cf0d71ae639fdd8097350bfac415a41aabf1d5ddd356295fdc95f09760382"
 dependencies = [
  "libm",
 ]
 
-[[package]]
-name = "stdlib_tests"
-version = "0.0.0"
-dependencies = [
- "indoc",
- "num-bigint",
- "qsc",
-]
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1387,9 +1388,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.38"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1398,15 +1399,15 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.12"
+version = "0.12.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
+checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
 
 [[package]]
 name = "termcolor"
-version = "1.3.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
 dependencies = [
  "winapi-util",
 ]
@@ -1434,18 +1435,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.49"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1464,9 +1465,9 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "1.34.0"
+version = "1.35.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
 dependencies = [
  "backtrace",
  "pin-project-lite",
@@ -1532,9 +1533,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -1542,9 +1543,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
 dependencies = [
  "bumpalo",
  "log",
@@ -1557,9 +1558,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.37"
+version = "0.4.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -1569,9 +1570,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1579,9 +1580,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1592,15 +1593,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
 
 [[package]]
 name = "web-sys"
-version = "0.3.64"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -1639,11 +1640,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-sys"
-version = "0.48.0"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -1652,13 +1653,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -1667,38 +1683,80 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/Cargo.toml b/Cargo.toml
index 069f3f22..4d90afd8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ members = [
     "fuzz",
     "katas",
     "language_service",
-    "library/tests",
+    "library",
     "pip",
     "resource_estimator",
     "wasm",
@@ -30,16 +30,16 @@ license = "MIT"
 version = "0.0.0"
 
 [workspace.dependencies]
-bitflags = "2.4.1"
+bitflags = "2.4.2"
 clap = "4.4"
 criterion = { version = "0.5", default-features = false }
-enum-iterator = "1.4"
-env_logger = "0.10.0"
+enum-iterator = "1.5"
+env_logger = "0.10"
 expect-test = "1.4"
 futures = "0.3"
 futures-util = "0.3"
 fasteval = "0.2"
-getrandom = { version = "0.2" }
+getrandom = "0.2"
 indoc = "2.0"
 js-sys = "0.3"
 libfuzzer-sys = "0.4"
@@ -58,11 +58,11 @@ serde-wasm-bindgen = "0.6"
 wasm-bindgen = "0.2"
 wasm-bindgen-futures = "0.4"
 rand = "0.8"
-serde_json = { version = "1.0" }
-pyo3 = { version = "0.20" }
-quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "f000e0066f56338595be37092cd0498f72ab10a2", default-features = false }
+serde_json = "1.0"
+pyo3 = "0.20"
+quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "e7de80bf06dcaf69367576ec31f901c0496a9832", default-features = false }
 async-trait = "0.1"
-tokio = { version = "1.34", features = ["macros", "rt"] }
+tokio = { version = "1.35", features = ["macros", "rt"] }
 
 [profile.release]
 debug = 1
diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs
index 9423da38..4b08c767 100644
--- a/compiler/qsc/benches/eval.rs
+++ b/compiler/qsc/benches/eval.rs
@@ -3,7 +3,7 @@
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use indoc::indoc;
-use qsc::{interpret::stateful, PackageType};
+use qsc::{interpret::Interpreter, PackageType};
 use qsc_eval::output::GenericReceiver;
 use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
 
@@ -14,7 +14,7 @@ const LARGE: &str = include_str!("./large.qs");
 pub fn teleport(c: &mut Criterion) {
     c.bench_function("Teleport evaluation", |b| {
         let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -32,7 +32,7 @@ pub fn teleport(c: &mut Criterion) {
 pub fn deutsch_jozsa(c: &mut Criterion) {
     c.bench_function("Deutsch-Jozsa evaluation", |b| {
         let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -50,7 +50,7 @@ pub fn deutsch_jozsa(c: &mut Criterion) {
 pub fn large_file(c: &mut Criterion) {
     c.bench_function("Large file parity evaluation", |b| {
         let sources = SourceMap::new([("large.qs".into(), LARGE.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -80,7 +80,7 @@ pub fn array_append(c: &mut Criterion) {
                 .into(),
             ),
         );
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -110,7 +110,7 @@ pub fn array_update(c: &mut Criterion) {
                 .into(),
             ),
         );
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index 323043fb..5e8264de 100644
--- a/compiler/qsc/src/bin/qsi.rs
+++ b/compiler/qsc/src/bin/qsi.rs
@@ -8,7 +8,7 @@ use clap::{crate_version, Parser};
 use miette::{Context, IntoDiagnostic, Report, Result};
 use num_bigint::BigUint;
 use num_complex::Complex64;
-use qsc::interpret::stateful::{self, InterpretResult, Interpreter};
+use qsc::interpret::{self, InterpretResult, Interpreter};
 use qsc_eval::{
     output::{self, Receiver},
     val::Value,
@@ -194,7 +194,7 @@ fn print_interpret_result(result: InterpretResult) {
     }
 }
 
-fn print_exec_result(result: Result<Value, Vec<stateful::Error>>) -> ExitCode {
+fn print_exec_result(result: Result<Value, Vec<interpret::Error>>) -> ExitCode {
     match result {
         Ok(value) => {
             println!("{value}");
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index d3894f69..5546eb59 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -2,11 +2,621 @@
 // Licensed under the MIT License.
 
 mod debug;
-pub mod stateful;
+
+#[cfg(test)]
+mod tests;
+
+#[cfg(test)]
+mod stepping_tests;
 
 pub use qsc_eval::{
     debug::Frame,
     output::{self, GenericReceiver},
     val::Value,
-    Error, StepAction, StepResult,
+    StepAction, StepResult,
+};
+
+use crate::{
+    error::{self, WithStack},
+    incremental::Compiler,
+};
+use debug::format_call_stack;
+use miette::Diagnostic;
+use num_bigint::BigUint;
+use num_complex::Complex;
+use qsc_codegen::qir_base::BaseProfSim;
+use qsc_data_structures::span::Span;
+use qsc_eval::{
+    backend::{Backend, SparseSim},
+    debug::{map_fir_package_to_hir, map_hir_package_to_fir},
+    eval_expr, eval_stmt,
+    output::Receiver,
+    val::{self},
+    Env, State, VariableInfo,
 };
+use qsc_fir::fir::{self, Global, PackageStoreLookup};
+use qsc_fir::{
+    fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
+    visit::{self, Visitor},
+};
+use qsc_frontend::{
+    compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap},
+    error::WithSource,
+};
+use qsc_passes::PackageType;
+use rustc_hash::FxHashSet;
+use thiserror::Error;
+
+impl Error {
+    #[must_use]
+    pub fn stack_trace(&self) -> &Option<String> {
+        match &self {
+            Error::Eval(err) => err.stack_trace(),
+            _ => &None,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Diagnostic, Error)]
+pub enum Error {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Compile(#[from] crate::compile::Error),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Pass(#[from] WithSource<qsc_passes::Error>),
+    #[error("runtime error")]
+    #[diagnostic(transparent)]
+    Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
+    #[error("entry point not found")]
+    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
+    NoEntryPoint,
+    #[error("unsupported runtime capabilities for code generation")]
+    #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
+    UnsupportedRuntimeCapabilities,
+}
+
+/// A Q# interpreter.
+pub struct Interpreter {
+    /// The incremental Q# compiler.
+    compiler: Compiler,
+    /// The runtime capabilities used for compilation.
+    capabilities: RuntimeCapabilityFlags,
+    /// The number of lines that have so far been compiled.
+    /// This field is used to generate a unique label
+    /// for each line evaluated with `eval_fragments`.
+    lines: u32,
+    // The FIR store
+    fir_store: fir::PackageStore,
+    /// FIR lowerer
+    lowerer: qsc_eval::lower::Lowerer,
+    /// The ID of the current package.
+    /// This ID is valid both for the FIR store and the `PackageStore`.
+    package: PackageId,
+    /// The ID of the source package. The source package
+    /// is made up of the initial sources passed in when creating the interpreter.
+    /// This ID is valid both for the FIR store and the `PackageStore`.
+    source_package: PackageId,
+    /// The default simulator backend.
+    sim: SparseSim,
+    /// The evaluator environment.
+    env: Env,
+    /// The current state of the evaluator.
+    state: State,
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub type InterpretResult = Result<Value, Vec<Error>>;
+
+impl Interpreter {
+    /// Creates a new incremental compiler, compiling the passed in sources.
+    /// # Errors
+    /// If compiling the sources fails, compiler errors are returned.
+    pub fn new(
+        std: bool,
+        sources: SourceMap,
+        package_type: PackageType,
+        capabilities: RuntimeCapabilityFlags,
+    ) -> Result<Self, Vec<Error>> {
+        let mut lowerer = qsc_eval::lower::Lowerer::new();
+        let mut fir_store = fir::PackageStore::new();
+
+        let compiler =
+            Compiler::new(std, sources, package_type, capabilities).map_err(into_errors)?;
+
+        for (id, unit) in compiler.package_store() {
+            fir_store.insert(
+                map_hir_package_to_fir(id),
+                lowerer.lower_package(&unit.package),
+            );
+        }
+
+        let source_package_id = compiler.source_package_id();
+        let package_id = compiler.package_id();
+
+        Ok(Self {
+            compiler,
+            lines: 0,
+            capabilities,
+            fir_store,
+            lowerer,
+            env: Env::with_empty_scope(),
+            sim: SparseSim::new(),
+            state: State::new(map_hir_package_to_fir(source_package_id)),
+            package: map_hir_package_to_fir(package_id),
+            source_package: map_hir_package_to_fir(source_package_id),
+        })
+    }
+
+    /// Loads the entry expression to the top of the evaluation stack.
+    /// This is needed for debugging so that when begging to debug with
+    /// a step action the system is already in the correct state.
+    /// # Errors
+    /// Returns a vector of errors if loading the entry point fails.
+    pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        qsc_eval::eval_push_expr(&mut self.state, expr);
+        Ok(())
+    }
+
+    /// Resumes execution with specified `StepAction`.
+    /// # Errors
+    /// Returns a vector of errors if evaluating the entry point fails.
+    pub fn eval_step(
+        &mut self,
+        receiver: &mut impl Receiver,
+        breakpoints: &[StmtId],
+        step: StepAction,
+    ) -> Result<StepResult, Vec<Error>> {
+        self.state
+            .eval(
+                &self.fir_store,
+                &mut self.env,
+                &mut self.sim,
+                receiver,
+                breakpoints,
+                step,
+            )
+            .map_err(|(error, call_stack)| {
+                eval_error(
+                    self.compiler.package_store(),
+                    &self.fir_store,
+                    call_stack,
+                    error,
+                )
+            })
+    }
+
+    /// Executes the entry expression until the end of execution.
+    /// # Errors
+    /// Returns a vector of errors if evaluating the entry point fails.
+    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        eval_expr(
+            &mut State::new(self.source_package),
+            expr,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            &mut self.sim,
+            receiver,
+        )
+        .map_err(|(error, call_stack)| {
+            eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )
+        })
+    }
+
+    /// Executes the entry expression until the end of execution, using the given simulator backend
+    /// and a new instance of the environment.
+    pub fn eval_entry_with_sim(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+    ) -> Result<Value, Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        eval_expr(
+            &mut State::new(self.source_package),
+            expr,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            sim,
+            receiver,
+        )
+        .map_err(|(error, call_stack)| {
+            eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )
+        })
+    }
+
+    fn get_entry_expr(&self) -> Result<ExprId, Vec<Error>> {
+        let unit = self
+            .fir_store
+            .get(self.source_package)
+            .expect("store should have package");
+        if let Some(entry) = unit.entry {
+            return Ok(entry);
+        };
+        Err(vec![Error::NoEntryPoint])
+    }
+
+    /// # Errors
+    /// If the parsing of the fragments fails, an error is returned.
+    /// If the compilation of the fragments fails, an error is returned.
+    /// If there is a runtime error when interpreting the fragments, an error is returned.
+    pub fn eval_fragments(
+        &mut self,
+        receiver: &mut impl Receiver,
+        fragments: &str,
+    ) -> InterpretResult {
+        let label = self.next_line_label();
+
+        let increment = self
+            .compiler
+            .compile_fragments_fail_fast(&label, fragments)
+            .map_err(into_errors)?;
+
+        let stmts = self.lower(&increment);
+
+        // Updating the compiler state with the new AST/HIR nodes
+        // is not necessary for the interpreter to function, as all
+        // the state required for evaluation already exists in the
+        // FIR store. It could potentially save some memory
+        // *not* to do hold on to the AST/HIR, but it is done
+        // here to keep the package stores consistent.
+        self.compiler.update(increment);
+
+        let mut result = Value::unit();
+
+        for stmt_id in stmts {
+            match eval_stmt(
+                stmt_id,
+                &self.fir_store,
+                &mut self.env,
+                &mut self.sim,
+                self.package,
+                receiver,
+            ) {
+                Ok(value) => result = value,
+                Err((error, call_stack)) => {
+                    return Err(eval_error(
+                        self.compiler.package_store(),
+                        &self.fir_store,
+                        call_stack,
+                        error,
+                    ))
+                }
+            }
+        }
+
+        Ok(result)
+    }
+
+    /// Runs the given entry expression on a new instance of the environment and simulator,
+    /// but using the current compilation.
+    pub fn run(
+        &mut self,
+        receiver: &mut impl Receiver,
+        expr: &str,
+    ) -> Result<InterpretResult, Vec<Error>> {
+        self.run_with_sim(&mut SparseSim::new(), receiver, expr)
+    }
+
+    /// Gets the current quantum state of the simulator.
+    pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
+        self.sim.capture_quantum_state()
+    }
+
+    /// Performs QIR codegen using the given entry expression on a new instance of the environment
+    /// and simulator but using the current compilation.
+    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
+        if self.capabilities != RuntimeCapabilityFlags::empty() {
+            return Err(vec![Error::UnsupportedRuntimeCapabilities]);
+        }
+
+        let mut sim = BaseProfSim::new();
+        let mut stdout = std::io::sink();
+        let mut out = GenericReceiver::new(&mut stdout);
+
+        let val = self.run_with_sim(&mut sim, &mut out, expr)??;
+
+        Ok(sim.finish(&val))
+    }
+
+    /// Runs the given entry expression on the given simulator with a new instance of the environment
+    /// but using the current compilation.
+    pub fn run_with_sim(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        expr: &str,
+    ) -> Result<InterpretResult, Vec<Error>> {
+        let stmt_id = self.compile_expr_to_stmt(expr)?;
+
+        Ok(self.run_internal(sim, receiver, stmt_id))
+    }
+
+    fn run_internal(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        stmt_id: StmtId,
+    ) -> InterpretResult {
+        match eval_stmt(
+            stmt_id,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            sim,
+            self.package,
+            receiver,
+        ) {
+            Ok(value) => Ok(value),
+            Err((error, call_stack)) => Err(eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )),
+        }
+    }
+
+    fn compile_expr_to_stmt(&mut self, expr: &str) -> Result<StmtId, Vec<Error>> {
+        let increment = self.compiler.compile_expr(expr).map_err(into_errors)?;
+
+        let stmts = self.lower(&increment);
+
+        // Updating the compiler state with the new AST/HIR nodes
+        // is not necessary for the interpreter to function, as all
+        // the state required for evaluation already exists in the
+        // FIR store. It could potentially save some memory
+        // *not* to do hold on to the AST/HIR, but it is done
+        // here to keep the package stores consistent.
+        self.compiler.update(increment);
+
+        assert!(stmts.len() == 1, "expected exactly one statement");
+        let stmt_id = stmts.first().expect("expected exactly one statement");
+
+        Ok(*stmt_id)
+    }
+
+    fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
+        let fir_package = self
+            .fir_store
+            .get_mut(self.package)
+            .expect("package should be in store");
+        self.lowerer
+            .lower_and_update_package(fir_package, &unit_addition.hir)
+    }
+
+    fn source_package(&self) -> &CompileUnit {
+        self.compiler
+            .package_store()
+            .get(map_fir_package_to_hir(self.source_package))
+            .expect("Could not load package")
+    }
+
+    fn next_line_label(&mut self) -> String {
+        let label = format!("line_{}", self.lines);
+        self.lines += 1;
+        label
+    }
+
+    #[must_use]
+    pub fn get_stack_frames(&self) -> Vec<StackFrame> {
+        let frames = self.state.get_stack_frames();
+        let stack_frames = frames
+            .iter()
+            .map(|frame| {
+                let callable = self
+                    .fir_store
+                    .get_global(frame.id)
+                    .expect("frame should exist");
+                let functor = format!("{}", frame.functor);
+                let name = match callable {
+                    Global::Callable(decl) => decl.name.name.to_string(),
+                    Global::Udt => "udt".into(),
+                };
+
+                let hir_package = self
+                    .compiler
+                    .package_store()
+                    .get(map_fir_package_to_hir(frame.id.package))
+                    .expect("package should exist");
+                let source = hir_package
+                    .sources
+                    .find_by_offset(frame.span.lo)
+                    .expect("frame should have a source");
+                let path = source.name.to_string();
+                StackFrame {
+                    name,
+                    functor,
+                    path,
+                    lo: frame.span.lo - source.offset,
+                    hi: frame.span.hi - source.offset,
+                }
+            })
+            .collect();
+        stack_frames
+    }
+
+    pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
+        self.sim.capture_quantum_state()
+    }
+
+    #[must_use]
+    pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
+        let unit = self.source_package();
+
+        if let Some(source) = unit.sources.find_by_name(path) {
+            let package = self
+                .fir_store
+                .get(self.source_package)
+                .expect("package should have been lowered");
+            let mut collector = BreakpointCollector::new(&unit.sources, source.offset, package);
+            collector.visit_package(package);
+            let mut spans: Vec<_> = collector
+                .statements
+                .iter()
+                .map(|bps| BreakpointSpan {
+                    id: bps.id,
+                    lo: bps.lo,
+                    hi: bps.hi,
+                })
+                .collect();
+            spans.sort_by_key(|s| s.lo);
+            spans
+        } else {
+            Vec::new()
+        }
+    }
+
+    #[must_use]
+    pub fn get_locals(&self) -> Vec<VariableInfo> {
+        self.env
+            .get_variables_in_top_frame()
+            .into_iter()
+            .filter(|v| !v.name.starts_with('@'))
+            .collect()
+    }
+}
+
+/// Represents a stack frame for debugging.
+pub struct StackFrame {
+    /// The name of the callable.
+    pub name: String,
+    /// The functor of the callable.
+    pub functor: String,
+    /// The path of the source file.
+    pub path: String,
+    /// The start of the call site span in utf8 characters.
+    pub lo: u32,
+    /// The end of the call site span in utf8 characters.
+    pub hi: u32,
+}
+
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct BreakpointSpan {
+    /// The id of the statement representing the breakpoint location.
+    pub id: u32,
+    /// The start of the call site span in utf8 characters.
+    pub lo: u32,
+    /// The end of the call site span in utf8 characters.
+    pub hi: u32,
+}
+
+struct BreakpointCollector<'a> {
+    statements: FxHashSet<BreakpointSpan>,
+    sources: &'a SourceMap,
+    offset: u32,
+    package: &'a Package,
+}
+
+impl<'a> BreakpointCollector<'a> {
+    fn new(sources: &'a SourceMap, offset: u32, package: &'a Package) -> Self {
+        Self {
+            statements: FxHashSet::default(),
+            sources,
+            offset,
+            package,
+        }
+    }
+
+    fn get_source(&self, offset: u32) -> &Source {
+        self.sources
+            .find_by_offset(offset)
+            .expect("Couldn't find source file")
+    }
+
+    fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
+        let source: &Source = self.get_source(stmt.span.lo);
+        if source.offset == self.offset {
+            let span = stmt.span - source.offset;
+            if span != Span::default() {
+                let bps = BreakpointSpan {
+                    id: stmt.id.into(),
+                    lo: span.lo,
+                    hi: span.hi,
+                };
+                self.statements.insert(bps);
+            }
+        }
+    }
+}
+
+impl<'a> Visitor<'a> for BreakpointCollector<'a> {
+    fn visit_stmt(&mut self, stmt: StmtId) {
+        let stmt_res = self.get_stmt(stmt);
+        match stmt_res.kind {
+            qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
+                self.add_stmt(stmt_res);
+                visit::walk_expr(self, expr);
+            }
+            qsc_fir::fir::StmtKind::Qubit(_, _, _, block) => match block {
+                Some(block) => visit::walk_block(self, block),
+                None => self.add_stmt(stmt_res),
+            },
+            qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
+                self.add_stmt(stmt_res);
+            }
+        };
+    }
+
+    fn get_block(&mut self, id: BlockId) -> &'a Block {
+        self.package
+            .blocks
+            .get(id)
+            .expect("couldn't find block in FIR")
+    }
+
+    fn get_expr(&mut self, id: ExprId) -> &'a Expr {
+        self.package
+            .exprs
+            .get(id)
+            .expect("couldn't find expr in FIR")
+    }
+
+    fn get_pat(&mut self, id: PatId) -> &'a Pat {
+        self.package.pats.get(id).expect("couldn't find pat in FIR")
+    }
+
+    fn get_stmt(&mut self, id: StmtId) -> &'a Stmt {
+        self.package
+            .stmts
+            .get(id)
+            .expect("couldn't find stmt in FIR")
+    }
+}
+
+fn eval_error(
+    package_store: &PackageStore,
+    fir_store: &fir::PackageStore,
+    call_stack: Vec<Frame>,
+    error: qsc_eval::Error,
+) -> Vec<Error> {
+    let stack_trace = if call_stack.is_empty() {
+        None
+    } else {
+        Some(format_call_stack(
+            package_store,
+            fir_store,
+            call_stack,
+            &error,
+        ))
+    };
+
+    vec![error::from_eval(error, package_store, stack_trace).into()]
+}
+
+fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
+    errors
+        .into_iter()
+        .map(|error| Error::Compile(error.into_with_source()))
+        .collect::<Vec<_>>()
+}
diff --git a/compiler/qsc/src/interpret/stateful.rs b/compiler/qsc/src/interpret/stateful.rs
deleted file mode 100644
index 9ebaf982..00000000
--- a/compiler/qsc/src/interpret/stateful.rs
+++ /dev/null
@@ -1,632 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-#[cfg(test)]
-mod tests;
-
-#[cfg(test)]
-mod stepping_tests;
-
-use super::debug::format_call_stack;
-use crate::{
-    error::{self, WithStack},
-    incremental::Compiler,
-};
-use miette::Diagnostic;
-use num_bigint::BigUint;
-use num_complex::Complex;
-use qsc_codegen::qir_base::BaseProfSim;
-use qsc_data_structures::span::Span;
-use qsc_eval::{
-    backend::{Backend, SparseSim},
-    debug::{map_fir_package_to_hir, map_hir_package_to_fir, Frame},
-    eval_expr, eval_stmt,
-    output::{GenericReceiver, Receiver},
-    val::{self, Value},
-    Env, State, StepAction, StepResult, VariableInfo,
-};
-use qsc_fir::fir::{self, Global, PackageStoreLookup};
-use qsc_fir::{
-    fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
-    visit::{self, Visitor},
-};
-use qsc_frontend::{
-    compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap},
-    error::WithSource,
-};
-use qsc_passes::PackageType;
-use rustc_hash::FxHashSet;
-use thiserror::Error;
-
-impl Error {
-    #[must_use]
-    pub fn stack_trace(&self) -> &Option<String> {
-        match &self {
-            Error::Eval(err) => err.stack_trace(),
-            _ => &None,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Diagnostic, Error)]
-pub enum Error {
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Compile(#[from] crate::compile::Error),
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Pass(#[from] WithSource<qsc_passes::Error>),
-    #[error("runtime error")]
-    #[diagnostic(transparent)]
-    Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
-    #[error("entry point not found")]
-    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
-    NoEntryPoint,
-    #[error("unsupported runtime capabilities for code generation")]
-    #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
-    UnsupportedRuntimeCapabilities,
-}
-
-/// A Q# interpreter.
-pub struct Interpreter {
-    /// The incremental Q# compiler.
-    compiler: Compiler,
-    /// The runtime capabilities used for compilation.
-    capabilities: RuntimeCapabilityFlags,
-    /// The number of lines that have so far been compiled.
-    /// This field is used to generate a unique label
-    /// for each line evaluated with `eval_fragments`.
-    lines: u32,
-    // The FIR store
-    fir_store: fir::PackageStore,
-    /// FIR lowerer
-    lowerer: qsc_eval::lower::Lowerer,
-    /// The ID of the current package.
-    /// This ID is valid both for the FIR store and the `PackageStore`.
-    package: PackageId,
-    /// The ID of the source package. The source package
-    /// is made up of the initial sources passed in when creating the interpreter.
-    /// This ID is valid both for the FIR store and the `PackageStore`.
-    source_package: PackageId,
-    /// The default simulator backend.
-    sim: SparseSim,
-    /// The evaluator environment.
-    env: Env,
-    /// The current state of the evaluator.
-    state: State,
-}
-
-pub type InterpretResult = Result<Value, Vec<Error>>;
-
-impl Interpreter {
-    /// Creates a new incremental compiler, compiling the passed in sources.
-    /// # Errors
-    /// If compiling the sources fails, compiler errors are returned.
-    pub fn new(
-        std: bool,
-        sources: SourceMap,
-        package_type: PackageType,
-        capabilities: RuntimeCapabilityFlags,
-    ) -> Result<Self, Vec<Error>> {
-        let mut lowerer = qsc_eval::lower::Lowerer::new();
-        let mut fir_store = fir::PackageStore::new();
-
-        let compiler =
-            Compiler::new(std, sources, package_type, capabilities).map_err(into_errors)?;
-
-        for (id, unit) in compiler.package_store() {
-            fir_store.insert(
-                map_hir_package_to_fir(id),
-                lowerer.lower_package(&unit.package),
-            );
-        }
-
-        let source_package_id = compiler.source_package_id();
-        let package_id = compiler.package_id();
-
-        Ok(Self {
-            compiler,
-            lines: 0,
-            capabilities,
-            fir_store,
-            lowerer,
-            env: Env::with_empty_scope(),
-            sim: SparseSim::new(),
-            state: State::new(map_hir_package_to_fir(source_package_id)),
-            package: map_hir_package_to_fir(package_id),
-            source_package: map_hir_package_to_fir(source_package_id),
-        })
-    }
-
-    /// Loads the entry expression to the top of the evaluation stack.
-    /// This is needed for debugging so that when begging to debug with
-    /// a step action the system is already in the correct state.
-    /// # Errors
-    /// Returns a vector of errors if loading the entry point fails.
-    pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        qsc_eval::eval_push_expr(&mut self.state, expr);
-        Ok(())
-    }
-
-    /// Resumes execution with specified `StepAction`.
-    /// # Errors
-    /// Returns a vector of errors if evaluating the entry point fails.
-    pub fn eval_step(
-        &mut self,
-        receiver: &mut impl Receiver,
-        breakpoints: &[StmtId],
-        step: StepAction,
-    ) -> Result<StepResult, Vec<Error>> {
-        self.state
-            .eval(
-                &self.fir_store,
-                &mut self.env,
-                &mut self.sim,
-                receiver,
-                breakpoints,
-                step,
-            )
-            .map_err(|(error, call_stack)| {
-                eval_error(
-                    self.compiler.package_store(),
-                    &self.fir_store,
-                    call_stack,
-                    error,
-                )
-            })
-    }
-
-    /// Executes the entry expression until the end of execution.
-    /// # Errors
-    /// Returns a vector of errors if evaluating the entry point fails.
-    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        eval_expr(
-            &mut State::new(self.source_package),
-            expr,
-            &self.fir_store,
-            &mut Env::with_empty_scope(),
-            &mut self.sim,
-            receiver,
-        )
-        .map_err(|(error, call_stack)| {
-            eval_error(
-                self.compiler.package_store(),
-                &self.fir_store,
-                call_stack,
-                error,
-            )
-        })
-    }
-
-    /// Executes the entry expression until the end of execution, using the given simulator backend
-    /// and a new instance of the environment.
-    pub fn eval_entry_with_sim(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-    ) -> Result<Value, Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        eval_expr(
-            &mut State::new(self.source_package),
-            expr,
-            &self.fir_store,
-            &mut Env::with_empty_scope(),
-            sim,
-            receiver,
-        )
-        .map_err(|(error, call_stack)| {
-            eval_error(
-                self.compiler.package_store(),
-                &self.fir_store,
-                call_stack,
-                error,
-            )
-        })
-    }
-
-    fn get_entry_expr(&self) -> Result<ExprId, Vec<Error>> {
-        let unit = self
-            .fir_store
-            .get(self.source_package)
-            .expect("store should have package");
-        if let Some(entry) = unit.entry {
-            return Ok(entry);
-        };
-        Err(vec![Error::NoEntryPoint])
-    }
-
-    /// # Errors
-    /// If the parsing of the fragments fails, an error is returned.
-    /// If the compilation of the fragments fails, an error is returned.
-    /// If there is a runtime error when interpreting the fragments, an error is returned.
-    pub fn eval_fragments(
-        &mut self,
-        receiver: &mut impl Receiver,
-        fragments: &str,
-    ) -> InterpretResult {
-        let label = self.next_line_label();
-
-        let increment = self
-            .compiler
-            .compile_fragments_fail_fast(&label, fragments)
-            .map_err(into_errors)?;
-
-        let stmts = self.lower(&increment);
-
-        // Updating the compiler state with the new AST/HIR nodes
-        // is not necessary for the interpreter to function, as all
-        // the state required for evaluation already exists in the
-        // FIR store. It could potentially save some memory
-        // *not* to do hold on to the AST/HIR, but it is done
-        // here to keep the package stores consistent.
-        self.compiler.update(increment);
-
-        let mut result = Value::unit();
-
-        for stmt_id in stmts {
-            match eval_stmt(
-                stmt_id,
-                &self.fir_store,
-                &mut self.env,
-                &mut self.sim,
-                self.package,
-                receiver,
-            ) {
-                Ok(value) => result = value,
-                Err((error, call_stack)) => {
-                    return Err(eval_error(
-                        self.compiler.package_store(),
-                        &self.fir_store,
-                        call_stack,
-                        error,
-                    ))
-                }
-            }
-        }
-
-        Ok(result)
-    }
-
-    /// Runs the given entry expression on a new instance of the environment and simulator,
-    /// but using the current compilation.
-    pub fn run(
-        &mut self,
-        receiver: &mut impl Receiver,
-        expr: &str,
-        shots: u32,
-    ) -> Result<Vec<InterpretResult>, Vec<Error>> {
-        self.run_with_sim(&mut SparseSim::new(), receiver, expr, shots)
-    }
-
-    /// Gets the current quantum state of the simulator.
-    pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
-        self.sim.capture_quantum_state()
-    }
-
-    /// Performs QIR codegen using the given entry expression on a new instance of the environment
-    /// and simulator but using the current compilation.
-    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
-        if self.capabilities != RuntimeCapabilityFlags::empty() {
-            return Err(vec![Error::UnsupportedRuntimeCapabilities]);
-        }
-
-        let mut sim = BaseProfSim::new();
-        let mut stdout = std::io::sink();
-        let mut out = GenericReceiver::new(&mut stdout);
-
-        let val = self
-            .run_with_sim(&mut sim, &mut out, expr, 1)?
-            .into_iter()
-            .last()
-            .expect("execution should have at least one result")?;
-
-        Ok(sim.finish(&val))
-    }
-
-    /// Runs the given entry expression on the given simulator with a new instance of the environment
-    /// but using the current compilation.
-    pub fn run_with_sim(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-        expr: &str,
-        shots: u32,
-    ) -> Result<Vec<InterpretResult>, Vec<Error>> {
-        let stmt_id = self.compile_expr_to_stmt(expr)?;
-
-        Ok(self.run_internal(sim, receiver, stmt_id, shots))
-    }
-
-    fn run_internal(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-        stmt_id: StmtId,
-        shots: u32,
-    ) -> Vec<InterpretResult> {
-        let mut results: Vec<InterpretResult> = Vec::new();
-        for i in 0..shots {
-            results.push(
-                match eval_stmt(
-                    stmt_id,
-                    &self.fir_store,
-                    &mut Env::with_empty_scope(),
-                    sim,
-                    self.package,
-                    receiver,
-                ) {
-                    Ok(value) => Ok(value),
-                    Err((error, call_stack)) => Err(eval_error(
-                        self.compiler.package_store(),
-                        &self.fir_store,
-                        call_stack,
-                        error,
-                    )),
-                },
-            );
-
-            if i != 0 {
-                // If running more than one shot, re-initialize the simulator to start the next shot
-                // from a clean state.
-                sim.reinit();
-            }
-        }
-
-        results
-    }
-
-    fn compile_expr_to_stmt(&mut self, expr: &str) -> Result<StmtId, Vec<Error>> {
-        let increment = self.compiler.compile_expr(expr).map_err(into_errors)?;
-
-        let stmts = self.lower(&increment);
-
-        // Updating the compiler state with the new AST/HIR nodes
-        // is not necessary for the interpreter to function, as all
-        // the state required for evaluation already exists in the
-        // FIR store. It could potentially save some memory
-        // *not* to do hold on to the AST/HIR, but it is done
-        // here to keep the package stores consistent.
-        self.compiler.update(increment);
-
-        assert!(stmts.len() == 1, "expected exactly one statement");
-        let stmt_id = stmts.first().expect("expected exactly one statement");
-
-        Ok(*stmt_id)
-    }
-
-    fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
-        let fir_package = self
-            .fir_store
-            .get_mut(self.package)
-            .expect("package should be in store");
-        self.lowerer
-            .lower_and_update_package(fir_package, &unit_addition.hir)
-    }
-
-    fn source_package(&self) -> &CompileUnit {
-        self.compiler
-            .package_store()
-            .get(map_fir_package_to_hir(self.source_package))
-            .expect("Could not load package")
-    }
-
-    fn next_line_label(&mut self) -> String {
-        let label = format!("line_{}", self.lines);
-        self.lines += 1;
-        label
-    }
-
-    #[must_use]
-    pub fn get_stack_frames(&self) -> Vec<StackFrame> {
-        let frames = self.state.get_stack_frames();
-        let stack_frames = frames
-            .iter()
-            .map(|frame| {
-                let callable = self
-                    .fir_store
-                    .get_global(frame.id)
-                    .expect("frame should exist");
-                let functor = format!("{}", frame.functor);
-                let name = match callable {
-                    Global::Callable(decl) => decl.name.name.to_string(),
-                    Global::Udt => "udt".into(),
-                };
-
-                let hir_package = self
-                    .compiler
-                    .package_store()
-                    .get(map_fir_package_to_hir(frame.id.package))
-                    .expect("package should exist");
-                let source = hir_package
-                    .sources
-                    .find_by_offset(frame.span.lo)
-                    .expect("frame should have a source");
-                let path = source.name.to_string();
-                StackFrame {
-                    name,
-                    functor,
-                    path,
-                    lo: frame.span.lo - source.offset,
-                    hi: frame.span.hi - source.offset,
-                }
-            })
-            .collect();
-        stack_frames
-    }
-
-    pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
-        self.sim.capture_quantum_state()
-    }
-
-    #[must_use]
-    pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
-        let unit = self.source_package();
-
-        if let Some(source) = unit.sources.find_by_name(path) {
-            let package = self
-                .fir_store
-                .get(self.source_package)
-                .expect("package should have been lowered");
-            let mut collector = BreakpointCollector::new(&unit.sources, source.offset, package);
-            collector.visit_package(package);
-            let mut spans: Vec<_> = collector
-                .statements
-                .iter()
-                .map(|bps| BreakpointSpan {
-                    id: bps.id,
-                    lo: bps.lo,
-                    hi: bps.hi,
-                })
-                .collect();
-            spans.sort_by_key(|s| s.lo);
-            spans
-        } else {
-            Vec::new()
-        }
-    }
-
-    #[must_use]
-    pub fn get_locals(&self) -> Vec<VariableInfo> {
-        self.env
-            .get_variables_in_top_frame()
-            .into_iter()
-            .filter(|v| !v.name.starts_with('@'))
-            .collect()
-    }
-}
-
-/// Represents a stack frame for debugging.
-pub struct StackFrame {
-    /// The name of the callable.
-    pub name: String,
-    /// The functor of the callable.
-    pub functor: String,
-    /// The path of the source file.
-    pub path: String,
-    /// The start of the call site span in utf8 characters.
-    pub lo: u32,
-    /// The end of the call site span in utf8 characters.
-    pub hi: u32,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub struct BreakpointSpan {
-    /// The id of the statement representing the breakpoint location.
-    pub id: u32,
-    /// The start of the call site span in utf8 characters.
-    pub lo: u32,
-    /// The end of the call site span in utf8 characters.
-    pub hi: u32,
-}
-
-struct BreakpointCollector<'a> {
-    statements: FxHashSet<BreakpointSpan>,
-    sources: &'a SourceMap,
-    offset: u32,
-    package: &'a Package,
-}
-
-impl<'a> BreakpointCollector<'a> {
-    fn new(sources: &'a SourceMap, offset: u32, package: &'a Package) -> Self {
-        Self {
-            statements: FxHashSet::default(),
-            sources,
-            offset,
-            package,
-        }
-    }
-
-    fn get_source(&self, offset: u32) -> &Source {
-        self.sources
-            .find_by_offset(offset)
-            .expect("Couldn't find source file")
-    }
-
-    fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
-        let source: &Source = self.get_source(stmt.span.lo);
-        if source.offset == self.offset {
-            let span = stmt.span - source.offset;
-            if span != Span::default() {
-                let bps = BreakpointSpan {
-                    id: stmt.id.into(),
-                    lo: span.lo,
-                    hi: span.hi,
-                };
-                self.statements.insert(bps);
-            }
-        }
-    }
-}
-
-impl<'a> Visitor<'a> for BreakpointCollector<'a> {
-    fn visit_stmt(&mut self, stmt: StmtId) {
-        let stmt_res = self.get_stmt(stmt);
-        match stmt_res.kind {
-            qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
-                self.add_stmt(stmt_res);
-                visit::walk_expr(self, expr);
-            }
-            qsc_fir::fir::StmtKind::Qubit(_, _, _, block) => match block {
-                Some(block) => visit::walk_block(self, block),
-                None => self.add_stmt(stmt_res),
-            },
-            qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
-                self.add_stmt(stmt_res);
-            }
-        };
-    }
-
-    fn get_block(&mut self, id: BlockId) -> &'a Block {
-        self.package
-            .blocks
-            .get(id)
-            .expect("couldn't find block in FIR")
-    }
-
-    fn get_expr(&mut self, id: ExprId) -> &'a Expr {
-        self.package
-            .exprs
-            .get(id)
-            .expect("couldn't find expr in FIR")
-    }
-
-    fn get_pat(&mut self, id: PatId) -> &'a Pat {
-        self.package.pats.get(id).expect("couldn't find pat in FIR")
-    }
-
-    fn get_stmt(&mut self, id: StmtId) -> &'a Stmt {
-        self.package
-            .stmts
-            .get(id)
-            .expect("couldn't find stmt in FIR")
-    }
-}
-
-fn eval_error(
-    package_store: &PackageStore,
-    fir_store: &fir::PackageStore,
-    call_stack: Vec<Frame>,
-    error: qsc_eval::Error,
-) -> Vec<Error> {
-    let stack_trace = if call_stack.is_empty() {
-        None
-    } else {
-        Some(format_call_stack(
-            package_store,
-            fir_store,
-            call_stack,
-            &error,
-        ))
-    };
-
-    vec![error::from_eval(error, package_store, stack_trace).into()]
-}
-
-fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
-    errors
-        .into_iter()
-        .map(|error| Error::Compile(error.into_with_source()))
-        .collect::<Vec<_>>()
-}
diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs
index dbc0671c..82591cb7 100644
--- a/compiler/qsc_codegen/src/qir_base.rs
+++ b/compiler/qsc_codegen/src/qir_base.rs
@@ -437,10 +437,6 @@ impl Backend for BaseProfSim {
         // true to avoid a panic.
         true
     }
-
-    fn reinit(&mut self) {
-        *self = Self::default();
-    }
 }
 
 struct Qubit(HardwareId);
diff --git a/compiler/qsc_eval/src/backend.rs b/compiler/qsc_eval/src/backend.rs
index 22c7bb84..2cc80d31 100644
--- a/compiler/qsc_eval/src/backend.rs
+++ b/compiler/qsc_eval/src/backend.rs
@@ -38,7 +38,6 @@ pub trait Backend {
     fn qubit_release(&mut self, q: usize);
     fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize);
     fn qubit_is_zero(&mut self, q: usize) -> bool;
-    fn reinit(&mut self);
 
     fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
         None
@@ -194,10 +193,6 @@ impl Backend for SparseSim {
         self.sim.qubit_is_zero(q)
     }
 
-    fn reinit(&mut self) {
-        *self = Self::new();
-    }
-
     fn custom_intrinsic(&mut self, name: &str, _arg: Value) -> Option<Result<Value, String>> {
         match name {
             "BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
diff --git a/compiler/qsc_frontend/Cargo.toml b/compiler/qsc_frontend/Cargo.toml
index 61b09045..7d210d7d 100644
--- a/compiler/qsc_frontend/Cargo.toml
+++ b/compiler/qsc_frontend/Cargo.toml
@@ -18,6 +18,7 @@ qsc_ast = { path = "../qsc_ast" }
 qsc_data_structures = { path = "../qsc_data_structures" }
 qsc_hir = { path = "../qsc_hir" }
 qsc_parse = { path = "../qsc_parse" }
+library = { path = "../../library" }
 rustc-hash = { workspace = true }
 thiserror = { workspace = true }
 
diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs
index 12801371..909afefc 100644
--- a/compiler/qsc_frontend/src/compile.rs
+++ b/compiler/qsc_frontend/src/compile.rs
@@ -39,9 +39,9 @@ use thiserror::Error;
 bitflags! {
     #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
     pub struct RuntimeCapabilityFlags: u32 {
-        const ConditionalForwardBranching = 0b0000_0001;
+        const ForwardBranching = 0b0000_0001;
         const IntegerComputations = 0b0000_0010;
-        const FloatingPointComputation = 0b0000_0100;
+        const FloatingPointComputations = 0b0000_0100;
         const BackwardsBranching = 0b0000_1000;
         const HigherLevelConstructs = 0b0001_0000;
     }
@@ -405,19 +405,11 @@ pub fn core() -> CompileUnit {
         next_id: PackageId::CORE,
     };
 
-    let sources = SourceMap::new(
-        [
-            (
-                "core/core.qs".into(),
-                include_str!("../../../library/core/core.qs").into(),
-            ),
-            (
-                "core/qir.qs".into(),
-                include_str!("../../../library/core/qir.qs").into(),
-            ),
-        ],
-        None,
-    );
+    let core: Vec<(SourceName, SourceContents)> = library::CORE_LIB
+        .iter()
+        .map(|(name, contents)| ((*name).into(), (*contents).into()))
+        .collect();
+    let sources = SourceMap::new(core, None);
 
     let mut unit = compile(&store, &[], sources, RuntimeCapabilityFlags::empty());
     assert_no_errors(&unit.sources, &mut unit.errors);
@@ -431,71 +423,11 @@ pub fn core() -> CompileUnit {
 /// Panics if the standard library does not compile without errors.
 #[must_use]
 pub fn std(store: &PackageStore, capabilities: RuntimeCapabilityFlags) -> CompileUnit {
-    let sources = SourceMap::new(
-        [
-            (
-                "arrays.qs".into(),
-                include_str!("../../../library/std/arrays.qs").into(),
-            ),
-            (
-                "canon.qs".into(),
-                include_str!("../../../library/std/canon.qs").into(),
-            ),
-            (
-                "convert.qs".into(),
-                include_str!("../../../library/std/convert.qs").into(),
-            ),
-            (
-                "core.qs".into(),
-                include_str!("../../../library/std/core.qs").into(),
-            ),
-            (
-                "diagnostics.qs".into(),
-                include_str!("../../../library/std/diagnostics.qs").into(),
-            ),
-            (
-                "internal.qs".into(),
-                include_str!("../../../library/std/internal.qs").into(),
-            ),
-            (
-                "intrinsic.qs".into(),
-                include_str!("../../../library/std/intrinsic.qs").into(),
-            ),
-            (
-                "math.qs".into(),
-                include_str!("../../../library/std/math.qs").into(),
-            ),
-            (
-                "measurement.qs".into(),
-                include_str!("../../../library/std/measurement.qs").into(),
-            ),
-            (
-                "qir.qs".into(),
-                include_str!("../../../library/std/qir.qs").into(),
-            ),
-            (
-                "random.qs".into(),
-                include_str!("../../../library/std/random.qs").into(),
-            ),
-            (
-                "unstable_arithmetic.qs".into(),
-                include_str!("../../../library/std/unstable_arithmetic.qs").into(),
-            ),
-            (
-                "unstable_arithmetic_internal.qs".into(),
-                include_str!("../../../library/std/unstable_arithmetic_internal.qs").into(),
-            ),
-            (
-                "unstable_table_lookup.qs".into(),
-                include_str!("../../../library/std/unstable_table_lookup.qs").into(),
-            ),
-            (
-                "re.qs".into(),
-                include_str!("../../../library/std/re.qs").into(),
-            ),
-        ],
-        None,
-    );
+    let std: Vec<(SourceName, SourceContents)> = library::STD_LIB
+        .iter()
+        .map(|(name, contents)| ((*name).into(), (*contents).into()))
+        .collect();
+    let sources = SourceMap::new(std, None);
 
     let mut unit = compile(store, &[PackageId::CORE], sources, capabilities);
     assert_no_errors(&unit.sources, &mut unit.errors);
diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs
index d7b5f4b1..8ba1b6f6 100644
--- a/compiler/qsc_frontend/src/lower.rs
+++ b/compiler/qsc_frontend/src/lower.rs
@@ -231,9 +231,10 @@ impl With<'_> {
                     if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path)
                         if ConfigAttr::from_str(path.name.name.as_ref()).is_ok()))
                 {
-                    self.lowerer
-                        .errors
-                        .push(Error::InvalidAttrArgs("Full or Base", attr.arg.span));
+                    self.lowerer.errors.push(Error::InvalidAttrArgs(
+                        "Unrestricted or Base",
+                        attr.arg.span,
+                    ));
                 }
                 None
             }
diff --git a/compiler/qsc_frontend/src/typeck/infer.rs b/compiler/qsc_frontend/src/typeck/infer.rs
index cbb0df53..f66f4cea 100644
--- a/compiler/qsc_frontend/src/typeck/infer.rs
+++ b/compiler/qsc_frontend/src/typeck/infer.rs
@@ -897,7 +897,7 @@ fn check_has_field(
             }],
             Vec::new(),
         ),
-        (Err(()), Ty::Udt(_, Res::Item(id))) => {
+        (_, Ty::Udt(_, Res::Item(id))) => {
             match udts.get(id).and_then(|udt| udt.field_ty_by_name(&name)) {
                 Some(ty) => (
                     vec![Constraint::Eq {
diff --git a/compiler/qsc_project/src/js.rs b/compiler/qsc_project/src/js.rs
index 5b87f164..7ad65523 100644
--- a/compiler/qsc_project/src/js.rs
+++ b/compiler/qsc_project/src/js.rs
@@ -1,6 +1,8 @@
-use std::{convert::Infallible, path::PathBuf, sync::Arc};
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
 
 use crate::{DirEntry, EntryType};
+use std::{convert::Infallible, path::PathBuf, sync::Arc};
 
 #[derive(Debug)]
 pub struct JSFileEntry {
diff --git a/jupyterlab/src/index.ts b/jupyterlab/src/index.ts
index 854c1e9a..de2de4b0 100644
--- a/jupyterlab/src/index.ts
+++ b/jupyterlab/src/index.ts
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 import {
   JupyterFrontEnd,
   JupyterFrontEndPlugin,
diff --git a/katas/src/lib.rs b/katas/src/lib.rs
index b095c1bc..cb742e58 100644
--- a/katas/src/lib.rs
+++ b/katas/src/lib.rs
@@ -8,11 +8,7 @@
 mod tests;
 
 use qsc::{
-    interpret::{
-        output::Receiver,
-        stateful::{self, Interpreter},
-        Value,
-    },
+    interpret::{output::Receiver, Error, Interpreter, Value},
     target::Profile,
     PackageType, SourceContents, SourceMap, SourceName,
 };
@@ -31,7 +27,7 @@ pub const EXERCISE_ENTRY: &str = "Kata.Verification.CheckSolution()";
 pub fn check_solution(
     exercise_sources: Vec<(SourceName, SourceContents)>,
     receiver: &mut impl Receiver,
-) -> Result<bool, Vec<stateful::Error>> {
+) -> Result<bool, Vec<Error>> {
     let source_map = SourceMap::new(exercise_sources, Some(EXERCISE_ENTRY.into()));
     let mut interpreter: Interpreter = Interpreter::new(
         true,
diff --git a/library/src/lib.rs b/library/src/lib.rs
new file mode 100644
index 00000000..cb69dd68
--- /dev/null
+++ b/library/src/lib.rs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#![warn(clippy::mod_module_files, clippy::pedantic, clippy::unwrap_used)]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
+
+#[cfg(test)]
+mod tests;
+
+// The core prefix on the name is needed to disambiguate from the std
+// files of the same name. This comes in during debugging when we need
+// to load a core/std file from the library.
+pub const CORE_LIB: &[(&str, &str)] = &[
+    ("core/core.qs", include_str!("../core/core.qs")),
+    ("core/qir.qs", include_str!("../core/qir.qs")),
+];
+
+pub const STD_LIB: &[(&str, &str)] = &[
+    ("arrays.qs", include_str!("../std/arrays.qs")),
+    ("canon.qs", include_str!("../std/canon.qs")),
+    ("convert.qs", include_str!("../std/convert.qs")),
+    ("core.qs", include_str!("../std/core.qs")),
+    ("diagnostics.qs", include_str!("../std/diagnostics.qs")),
+    ("internal.qs", include_str!("../std/internal.qs")),
+    ("intrinsic.qs", include_str!("../std/intrinsic.qs")),
+    ("math.qs", include_str!("../std/math.qs")),
+    ("measurement.qs", include_str!("../std/measurement.qs")),
+    ("qir.qs", include_str!("../std/qir.qs")),
+    ("random.qs", include_str!("../std/random.qs")),
+    ("re.qs", include_str!("../std/re.qs")),
+    (
+        "unstable_arithmetic.qs",
+        include_str!("../std/unstable_arithmetic.qs"),
+    ),
+    (
+        "unstable_arithmetic_internal.qs",
+        include_str!("../std/unstable_arithmetic_internal.qs"),
+    ),
+    (
+        "unstable_table_lookup.qs",
+        include_str!("../std/unstable_table_lookup.qs"),
+    ),
+];
diff --git a/npm/src/ux/ticks.ts b/npm/src/ux/ticks.ts
new file mode 100644
index 00000000..495b832a
--- /dev/null
+++ b/npm/src/ux/ticks.ts
@@ -0,0 +1,164 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+export type Tick = {
+  value: number;
+  label: string;
+};
+
+// Should be redesigned in case of localization
+type TickTimeDefinition = { tick: Tick; plural: string };
+
+export function CreateIntegerTicks(min: number, max: number): Tick[] {
+  if (max < min || min <= 0) {
+    return [];
+  }
+
+  const numberFormat = new Intl.NumberFormat();
+
+  const l = Math.round(10 ** Math.floor(Math.log10(min)));
+  const r = Math.round(10 ** Math.ceil(Math.log10(max)));
+
+  const result: Tick[] = [];
+
+  for (let i = l; i <= r; i *= 10) {
+    if (i >= min && i <= max) {
+      result.push({ value: i, label: numberFormat.format(i) });
+    }
+  }
+  if (result.length == 0) {
+    let step = l;
+    while (step >= 1) {
+      let i = Math.ceil(min / step) * step;
+      while (i <= max) {
+        result.push({ value: i, label: numberFormat.format(i) });
+        i += step;
+      }
+      if (result.length > 0) {
+        break;
+      }
+      step /= 10;
+    }
+  }
+
+  return result;
+}
+
+const predefinedTimeTicks: TickTimeDefinition[] = [
+  { tick: { value: 1, label: "1 nanosecond" }, plural: "nanoseconds" },
+  { tick: { value: 1e3, label: "1 microsecond" }, plural: "microseconds" },
+  { tick: { value: 1e6, label: "1 millisecond" }, plural: "milliseconds" },
+  { tick: { value: 1e9, label: "1 second" }, plural: "seconds" },
+  { tick: { value: 6e10, label: "1 minute" }, plural: "minutes" },
+  { tick: { value: 3.6e12, label: "1 hour" }, plural: "hours" },
+  { tick: { value: 8.64e13, label: "1 day" }, plural: "days" },
+  { tick: { value: 6.048e14, label: "1 week" }, plural: "weeks" },
+  { tick: { value: 2.592e15, label: "1 month" }, plural: "months" },
+  { tick: { value: 3.1536e16, label: "1 year" }, plural: "years" },
+  { tick: { value: 3.1536e17, label: "1 decade" }, plural: "decades" },
+  { tick: { value: 3.1536e18, label: "1 century" }, plural: "centuries" },
+];
+
+export function CreateTimeTicks(min: number, max: number): Tick[] {
+  if (max < min || min <= 0) {
+    return [];
+  }
+
+  let l = 0;
+  while (
+    l < predefinedTimeTicks.length &&
+    predefinedTimeTicks[l].tick.value <= min
+  ) {
+    l++;
+  }
+
+  let r = l;
+  if (l > 0) {
+    l--;
+  }
+
+  if (r >= predefinedTimeTicks.length) {
+    r = predefinedTimeTicks.length - 1;
+  }
+
+  while (
+    r < predefinedTimeTicks.length - 1 &&
+    predefinedTimeTicks[r].tick.value <= max
+  ) {
+    r++;
+  }
+
+  const result: Tick[] = [];
+
+  for (let i = l; i <= r; i++) {
+    if (
+      predefinedTimeTicks[i].tick.value >= min &&
+      predefinedTimeTicks[i].tick.value <= max
+    ) {
+      result.push(predefinedTimeTicks[i].tick);
+    }
+  }
+
+  if (result.length == 0) {
+    if (l < predefinedTimeTicks.length - 1) {
+      let coeff =
+        10 **
+        Math.floor(
+          Math.log10(
+            predefinedTimeTicks[l + 1].tick.value /
+              predefinedTimeTicks[l].tick.value,
+          ),
+        );
+      do {
+        let i = 1;
+        let val = 0;
+        do {
+          val = predefinedTimeTicks[l].tick.value * i * coeff;
+          if (val >= min && val <= max) {
+            result.push({
+              value: val,
+              label:
+                (i * coeff).toString() + " " + predefinedTimeTicks[l].plural,
+            });
+          }
+          i++;
+        } while (
+          // just a single tick in case of 1, 10 or 100
+          (i > 2 || result.length < 1) &&
+          val < predefinedTimeTicks[l + 1].tick.value
+        );
+        coeff /= 10;
+      } while (result.length < 1 && coeff >= 1);
+    } else {
+      // > century
+      let l = predefinedTimeTicks.length - 1;
+      do {
+        let coeff =
+          10 ** Math.floor(Math.log10(max / predefinedTimeTicks[l].tick.value));
+        do {
+          let i = 1;
+          let val = 0;
+          do {
+            val = predefinedTimeTicks[l].tick.value * i * coeff;
+            if (val >= min && val <= max) {
+              result.push({
+                value: val,
+                label:
+                  (i * coeff).toString() + " " + predefinedTimeTicks[l].plural,
+              });
+            }
+            i++;
+          } while (
+            // just a single tick in case of 1, 10 or 100
+            (i > 2 || result.length < 1) &&
+            val < max
+          );
+          coeff /= 10;
+        } while (result.length < 1 && coeff >= 1);
+        l--;
+      } while (result.length < 1 && l >= 0);
+    }
+  }
+
+  return result;
+}
diff --git a/npm/ux/colormap.ts b/npm/ux/colormap.ts
new file mode 100644
index 00000000..fef64077
--- /dev/null
+++ b/npm/ux/colormap.ts
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+export function ColorMap(colorsCount: number): string[] {
+  const colors: string[] = [];
+  const saturationIncrement = 1.4142135623730951; // square root of 2
+  const hueIncrement = 0.618033988749895; // golden ratio
+  const lightnessIncrement = 2.718281828459045; // euler's number
+
+  for (let i = 0; i < colorsCount; i++) {
+    const hue = (i * hueIncrement) % 1;
+    const saturation = (1 - ((i * saturationIncrement) % 1)) * 0.5 + 0.5;
+    const lightness = ((i * lightnessIncrement) % 1) * 0.3 + 0.35;
+    const rgbColor = hslToRgb(hue, saturation, lightness);
+    const hexColor = rgbToHex(rgbColor[0], rgbColor[1], rgbColor[2]);
+    colors.push(hexColor);
+  }
+  return colors;
+}
+
+function hslToRgb(h: number, s: number, l: number): [number, number, number] {
+  let r, g, b;
+
+  if (s === 0) {
+    r = g = b = l; // Grayscale
+  } else {
+    const hue2rgb = (p: number, q: number, t: number) => {
+      if (t < 0) t += 1;
+      if (t > 1) t -= 1;
+      if (t < 1 / 6) return p + (q - p) * 6 * t;
+      if (t < 1 / 2) return q;
+      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+      return p;
+    };
+
+    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+    const p = 2 * l - q;
+    r = hue2rgb(p, q, h + 1 / 3);
+    g = hue2rgb(p, q, h);
+    b = hue2rgb(p, q, h - 1 / 3);
+  }
+
+  return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+}
+
+function rgbToHex(r: number, g: number, b: number): string {
+  return `#${((1 << 24) | (r << 16) | (g << 8) | b)
+    .toString(16)
+    .slice(1)
+    .toUpperCase()}`;
+}
diff --git a/npm/ux/data.ts b/npm/ux/data.ts
new file mode 100644
index 00000000..c6b9c9f6
--- /dev/null
+++ b/npm/ux/data.ts
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+export type ReData = {
+  status: string;
+  jobParams: any;
+  physicalCounts: any;
+  physicalCountsFormatted: any;
+  logicalQubit: any;
+  tfactory: any;
+  errorBudget: any;
+  logicalCounts: any;
+  new: boolean;
+  frontierEntries: FrontierEntry[];
+};
+
+export type SingleEstimateResult = {
+  status: string;
+  jobParams: any;
+  physicalCounts: any;
+  physicalCountsFormatted: any;
+  logicalQubit: any;
+  tfactory: any;
+  errorBudget: any;
+  logicalCounts: any;
+  new: boolean;
+};
+
+export type FrontierEntry = {
+  logicalQubit: any;
+  tfactory: any;
+  errorBudget: any;
+  physicalCounts: any;
+  physicalCountsFormatted: any;
+};
+
+export function CreateSingleEstimateResult(
+  input: ReData,
+  frontierEntryIndex?: number,
+): SingleEstimateResult {
+  if (
+    frontierEntryIndex == undefined ||
+    input.frontierEntries == null ||
+    input.frontierEntries.length === 0
+  ) {
+    return input;
+  } else {
+    if (
+      frontierEntryIndex < 0 ||
+      frontierEntryIndex >= input.frontierEntries.length
+    ) {
+      frontierEntryIndex = 0;
+    }
+
+    const entry = input.frontierEntries[frontierEntryIndex];
+    return {
+      status: input.status,
+      jobParams: input.jobParams,
+      physicalCounts: entry.physicalCounts,
+      physicalCountsFormatted: entry.physicalCountsFormatted,
+      logicalQubit: entry.logicalQubit,
+      tfactory: entry.tfactory,
+      errorBudget: entry.errorBudget,
+      logicalCounts: input.logicalCounts,
+      new: input.new,
+    };
+  }
+}
diff --git a/npm/ux/estimatesOverview.tsx b/npm/ux/estimatesOverview.tsx
new file mode 100644
index 00000000..6a0069e2
--- /dev/null
+++ b/npm/ux/estimatesOverview.tsx
@@ -0,0 +1,227 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// A component including the results table and the scatter chart together.
+// The results table is also a legend for the scatter chart.
+
+import { ColorMap } from "./colormap.js";
+import {
+  CreateSingleEstimateResult,
+  FrontierEntry,
+  ReData,
+  SingleEstimateResult,
+} from "./data.js";
+import { ResultsTable, Row } from "./resultsTable.js";
+import {
+  Axis,
+  HideTooltip,
+  PlotItem,
+  ScatterChart,
+  ScatterSeries,
+} from "./scatterChart.js";
+
+const columnNames = [
+  "Run name",
+  "Estimate type",
+  "Qubit type",
+  "QEC scheme",
+  "Error budget",
+  "Logical qubits",
+  "Logical depth",
+  "Code distance",
+  "T states",
+  "T factories",
+  "T factory fraction",
+  "Runtime",
+  "rQOPS",
+  "Physical qubits",
+];
+
+const initialColumns = [0, 1, 2, 3, 4, 10, 11, 12];
+
+const xAxis: Axis = {
+  isTime: true,
+  label: "Runtime",
+};
+
+const yAxis: Axis = {
+  isTime: false,
+  label: "Physical Qubits",
+};
+
+function reDataToRow(input: ReData, color: string): Row {
+  const data = CreateSingleEstimateResult(input, 0);
+  const estimateType =
+    input.frontierEntries == null
+      ? "Single"
+      : "Frontier (" + input.frontierEntries.length + "  items)";
+
+  return {
+    cells: [
+      data.jobParams.runName,
+      estimateType,
+      data.jobParams.qubitParams.name,
+      data.jobParams.qecScheme.name,
+      data.jobParams.errorBudget,
+      data.physicalCounts.breakdown.algorithmicLogicalQubits,
+      data.physicalCounts.breakdown.algorithmicLogicalDepth,
+      data.logicalQubit.codeDistance,
+      data.physicalCounts.breakdown.numTstates,
+      data.physicalCounts.breakdown.numTfactories,
+      data.physicalCountsFormatted.physicalQubitsForTfactoriesPercentage,
+      {
+        value: data.physicalCountsFormatted.runtime,
+        sortBy: data.physicalCounts.runtime,
+      },
+      data.physicalCounts.rqops,
+      data.physicalCounts.physicalQubits,
+      data.new ? "New" : "Cached",
+    ],
+    color: color,
+  };
+}
+
+function frontierEntryToPlotEntry(entry: FrontierEntry): PlotItem {
+  return {
+    x: entry.physicalCounts.runtime,
+    y: entry.physicalCounts.physicalQubits,
+    label:
+      entry.physicalCountsFormatted.runtime +
+      " " +
+      entry.physicalCountsFormatted.physicalQubits,
+  };
+}
+
+function reDataToRowScatter(data: ReData, color: string): ScatterSeries {
+  if (data.frontierEntries == null || data.frontierEntries.length === 0) {
+    return {
+      color: color,
+      items: [
+        {
+          x: data.physicalCounts.runtime,
+          y: data.physicalCounts.physicalQubits,
+          label:
+            data.physicalCountsFormatted.runtime +
+            " " +
+            data.physicalCountsFormatted.physicalQubits,
+        },
+      ],
+    };
+  }
+
+  return {
+    color: color,
+    items: data.frontierEntries.map(frontierEntryToPlotEntry),
+  };
+}
+
+export function EstimatesOverview(props: {
+  estimatesData: ReData[];
+  colors: string[] | null;
+  runNames: string[] | null;
+  isSimplifiedView: boolean;
+  onRowDeleted: (rowId: string) => void;
+  setEstimate: (estimate: SingleEstimateResult | null) => void;
+}) {
+  props.estimatesData.forEach((item, idx) => {
+    if (
+      props.runNames != null &&
+      props.runNames.length == props.estimatesData.length
+    ) {
+      item.jobParams.runName = props.runNames[idx];
+    } else {
+      if (item.jobParams.runName == null) {
+        // Start indexing with 0 to match with the original object indexing.
+        item.jobParams.runName = `(${idx})`;
+      }
+    }
+  });
+
+  function onPointSelected(seriesIndex: number, pointIndex: number): void {
+    const data = props.estimatesData[seriesIndex];
+    props.setEstimate(CreateSingleEstimateResult(data, pointIndex));
+  }
+
+  function onRowSelected(rowId: string) {
+    // On any selection, clear the "new" flag on all rows. This ensures that
+    // new rows do not steal focus from the user selected row.
+    props.estimatesData.forEach((data) => (data.new = false));
+    if (!rowId) {
+      props.setEstimate(null);
+    } else {
+      const estimateFound =
+        props.estimatesData.find((data) => data.jobParams.runName === rowId) ||
+        null;
+      if (estimateFound == null) {
+        props.setEstimate(null);
+      } else {
+        props.setEstimate(CreateSingleEstimateResult(estimateFound, 0));
+      }
+    }
+
+    HideTooltip();
+  }
+
+  const colormap =
+    props.colors != null && props.colors.length == props.estimatesData.length
+      ? props.colors
+      : ColorMap(props.estimatesData.length);
+
+  if (props.isSimplifiedView) {
+    return (
+      <>
+        <ResultsTable
+          columnNames={columnNames}
+          rows={props.estimatesData.map((dataItem, index) =>
+            reDataToRow(dataItem, colormap[index]),
+          )}
+          initialColumns={initialColumns}
+          ensureSelected={true}
+          onRowSelected={onRowSelected}
+          onRowDeleted={props.onRowDeleted}
+        />
+        <ScatterChart
+          xAxis={xAxis}
+          yAxis={yAxis}
+          data={props.estimatesData.map((dataItem, index) =>
+            reDataToRowScatter(dataItem, colormap[index]),
+          )}
+          onPointSelected={onPointSelected}
+        />
+      </>
+    );
+  }
+
+  return (
+    <>
+      <details open>
+        <summary style="font-size: 1.5em; font-weight: bold; margin: 24px 8px;">
+          Results
+        </summary>
+        <ResultsTable
+          columnNames={columnNames}
+          rows={props.estimatesData.map((dataItem, index) =>
+            reDataToRow(dataItem, colormap[index]),
+          )}
+          initialColumns={initialColumns}
+          ensureSelected={true}
+          onRowSelected={onRowSelected}
+          onRowDeleted={props.onRowDeleted}
+        />
+      </details>
+      <details open>
+        <summary style="font-size: 1.5em; font-weight: bold; margin: 24px 8px;">
+          Qubit-time diagram
+        </summary>
+        <ScatterChart
+          xAxis={xAxis}
+          yAxis={yAxis}
+          data={props.estimatesData.map((dataItem, index) =>
+            reDataToRowScatter(dataItem, colormap[index]),
+          )}
+          onPointSelected={onPointSelected}
+        />
+      </details>
+    </>
+  );
+}
diff --git a/npm/ux/generate_report_code.py b/npm/ux/generate_report_code.py
new file mode 100644
index 00000000..fa4abbef
--- /dev/null
+++ b/npm/ux/generate_report_code.py
@@ -0,0 +1,243 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# This script generates the code for the report page from the output_data.md file.
+# To run, simply execute `python generate_report_code.py` in the npm/ux folder.
+# copy the output and paste it into the report.tsx file.
+# It provides a code for the CreateReport function.
+
+import re
+
+parse = False
+
+title = ""
+always_visible = True
+first_entry = True
+entries = []
+
+label = ""
+path = ""
+value = ""
+description = ""
+explanation = ""
+
+assumptions = []
+
+ignore_paths = [
+    "floquet_code",
+    "surface_code",
+    "qubit_gate_ns_e3",
+    "qubit_gate_ns_e4",
+    "qubit_gate_us_e3",
+    "qubit_gate_us_e4",
+    "qubit_maj_ns_e4",
+    "qubit_maj_ns_e6",
+]
+
+path_map = {
+    "errorBudget/rotations": "result.errorBudget.rotations",
+    "jobParams/qecScheme/crossingPrefactor": "result.jobParams.qecScheme.crossingPrefactor",
+    "jobParams/qecScheme/errorCorrectionThreshold": "result.jobParams.qecScheme.errorCorrectionThreshold",
+    "jobParams/qecScheme/logicalCycleTime": "result.jobParams.qecScheme.logicalCycleTime",
+    "jobParams/qecScheme/physicalQubitsPerLogicalQubit": "result.jobParams.qecScheme.physicalQubitsPerLogicalQubit",
+    "jobParams/qubitParams/tGateErrorRate": "result.jobParams.qubitParams.tGateErrorRate",
+    "logicalCounts/ccixCount": "numberFormat.format(result.logicalCounts.ccixCount)",
+    "logicalCounts/cczCount": "numberFormat.format(result.logicalCounts.cczCount)",
+    "logicalCounts/measurementCount": "numberFormat.format(result.logicalCounts.measurementCount)",
+    "logicalCounts/numQubits": "numberFormat.format(result.logicalCounts.numQubits)",
+    "logicalCounts/rotationCount": "numberFormat.format(result.logicalCounts.rotationCount)",
+    "logicalCounts/rotationDepth": "numberFormat.format(result.logicalCounts.rotationDepth)",
+    "logicalCounts/tCount": "numberFormat.format(result.logicalCounts.tCount)",
+    "logicalQubit/codeDistance": "result.logicalQubit.codeDistance",
+    "logicalQubit/logicalCyclesPerSecond": "numberFormatF64.format(result.physicalCounts.breakdown.clockFrequency)",
+    "logicalQubit/logicalCycleTime": "numberFormat.format(result.logicalQubit.logicalCycleTime)",
+    "logicalQubit/physicalQubits": "numberFormat.format(result.logicalQubit.physicalQubits)",
+    "physicalCounts/breakdown/algorithmicLogicalDepth": "numberFormat.format(result.physicalCountsFormatted.algorithmicLogicalDepth)",
+    "physicalCounts/breakdown/algorithmicLogicalQubits": "numberFormat.format(result.physicalCounts.breakdown.algorithmicLogicalQubits)",
+    "physicalCounts/breakdown/cliffordErrorRate": "result.physicalCounts.breakdown.cliffordErrorRate",
+    "physicalCounts/breakdown/logicalDepth": "numberFormat.format(result.physicalCounts.breakdown.logicalDepth)",
+    "physicalCounts/breakdown/physicalQubitsForAlgorithm": "numberFormat.format(result.physicalCounts.breakdown.physicalQubitsForAlgorithm)",
+    "physicalCounts/breakdown/physicalQubitsForTfactories": "numberFormat.format(result.physicalCounts.breakdown.physicalQubitsForTfactories)",
+    "physicalCounts/breakdown/numTfactories": "numberFormat.format(result.physicalCounts.breakdown.numTfactories)",
+    "physicalCounts/breakdown/numTfactoryRuns": "numberFormat.format(result.physicalCounts.breakdown.numTfactoryRuns)",
+    "physicalCounts/breakdown/numTstates": "numberFormat.format(result.physicalCounts.breakdown.numTstates)",
+    "physicalCounts/breakdown/requiredLogicalQubitErrorRate": "result.physicalCounts.breakdown.requiredLogicalQubitErrorRate",
+    "physicalCounts/physicalQubits": "numberFormat.format(result.physicalCounts.physicalQubits)",
+    "physicalCounts/runtime": "numberFormat.format(result.physicalCounts.runtime)",
+    "physicalCountsFormatted/clockFrequency": "result.physicalCountsFormatted.clockFrequency",
+    "physicalCountsFormatted/errorBudget": "result.physicalCountsFormatted.errorBudget",
+    "physicalCountsFormatted/errorBudgetLogical": "result.physicalCountsFormatted.errorBudgetLogical",
+    "physicalCountsFormatted/errorBudgetRotations": "result.physicalCountsFormatted.errorBudgetRotations",
+    "physicalCountsFormatted/errorBudgetTstates": "result.physicalCountsFormatted.errorBudgetTstates",
+    "physicalCountsFormatted/logicalCycleTime": "result.physicalCountsFormatted.logicalCycleTime",
+    "physicalCountsFormatted/numTsPerRotation": "result.physicalCountsFormatted.numTsPerRotation",
+    "physicalCountsFormatted/requiredLogicalQubitErrorRate": "result.physicalCountsFormatted.requiredLogicalQubitErrorRate",
+    "physicalCountsFormatted/requiredLogicalTstateErrorRate": "result.physicalCountsFormatted.requiredLogicalTstateErrorRate",
+    "physicalCountsFormatted/runtime": "result.physicalCountsFormatted.runtime",
+    "physicalCountsFormatted/tfactoryRuntime": "result.physicalCountsFormatted.tfactoryRuntime",
+    "physicalCountsFormatted/tstateLogicalErrorRate": "result.physicalCountsFormatted.tstateLogicalErrorRate",
+    "tfactory/physicalQubits": "numberFormat.format(result.tfactory == null ? 0 : result.tfactory.physicalQubits)",
+    "tfactory/numInputTstates": "numberFormat.format(result.tfactory == null ? 0 : result.tfactory.numInputTstates)",
+    "tfactory/numTstates": "numberFormat.format(result.tfactory == null ? 0 : result.tfactory.numTstates)",
+    "tfactory/runtime": "numberFormat.format(result.tfactory == null ? 0 : result.tfactory.runtime)",
+}
+
+print()
+print()
+print(
+    "// THIS CODE HAS BEEN AUTOMATICALLY GENERATED WITH generate_report_code.py from output_data.md"
+)
+print("export function CreateReport(result: ReData): ReportData {")
+print("    const groups = [] as ReportGroup[];")
+print("    let entries = [] as ReportEntry[];")
+print("    const numberFormat = new Intl.NumberFormat();")
+print(
+    "    const numberFormatF64 = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2,});"
+)
+print()
+
+
+def add_group():
+    global always_visible, entries, title
+
+    if len(entries) != 0:
+        if title == "T factory parameters":
+            print("    if (result.tfactory != null) {")
+
+        always_visible_str = "true" if always_visible else "false"
+        print("    entries = [];")
+        for path, label, description, explanation in entries:
+            if path in [
+                "jobParams/qubitParams/oneQubitGateTime",
+                "jobParams/qubitParams/twoQubitGateTime",
+                "jobParams/qubitParams/oneQubitGateErrorRate",
+                "jobParams/qubitParams/twoQubitGateErrorRate",
+            ]:
+                print(
+                    '    if (result.jobParams.qubitParams.instructionSet == "GateBased") {'
+                )
+                print(
+                    f'        entries.push({{path: "{path}", label: "{label}", description: {description}, explanation: {explanation}}});'
+                )
+                print("        }")
+            elif path in [
+                "jobParams/qubitParams/twoQubitJointMeasurementTime",
+                "jobParams/qubitParams/twoQubitJointMeasurementErrorRate",
+            ]:
+                print(
+                    '    if (result.jobParams.qubitParams.instructionSet == "Majorana") {'
+                )
+                print(
+                    f'        entries.push({{path: "{path}", label: "{label}", description: {description}, explanation: {explanation}}});'
+                )
+                print("    }")
+            else:
+                print(
+                    f'        entries.push({{path: "{path}", label: "{label}", description: {description}, explanation: {explanation}}});'
+                )
+        print(
+            f'    groups.push({{ title: "{title}", alwaysVisible: {always_visible_str}, entries: entries }});'
+        )
+        print()
+
+        if title == "T factory parameters":
+            print("    }")
+
+        always_visible = False
+        entries.clear()
+
+
+def create_fmt_string(string):
+    args = []
+
+    # Find in-text `paths`
+    pos = string.find("`")
+    while pos != -1:
+        pos2 = string.find("`", pos + 1)
+        path = string[pos + 1 : pos2]
+
+        if path in ignore_paths:
+            pos = string.find("`", pos2 + 1)
+        else:
+            string = string[:pos] + "${" + path_map[path] + "}" + string[pos2 + 1 :]
+            pos = string.find("`", pos + 1)
+
+    # Find in-math \mathtt{paths}
+    pos = string.find("\\mathtt{")
+    while pos != -1:
+        pos2 = string.find("}", pos + 1)
+        path = string[pos + 8 : pos2]
+
+        string = string[:pos] + "${" + path_map[path] + "}" + string[pos2 + 1 :]
+        pos = string.find("\\mathtt{", pos + 1)
+
+    if len(args) != 0:
+        args_list = ", ".join(args)
+
+        cur = 0
+        while cur < len(string):
+            if string[cur] == "{" and string[cur + 1] != "}":
+                string = string[:cur] + "{" + string[cur:]
+                cur += 2
+            elif string[cur] == "}" and string[cur - 1] != "{":
+                string = string[:cur] + "}" + string[cur:]
+                cur += 2
+            else:
+                cur += 1
+
+    string = string.replace("\\", "\\\\")
+    string = string.replace("`", "\\`")
+    return f"`{string}`"
+
+
+with open("output_data.md", "r") as f:
+    for line in f.readlines():
+        line = line.strip()
+
+        if line == "":
+            continue
+
+        if not parse:
+            if line.startswith("## "):
+                parse = True
+            else:
+                continue
+
+        if line.startswith("## "):
+            # Finish previous group?
+            if len(entries) != 0:
+                add_group()
+
+            title = line[3:].strip()
+        elif line.startswith("### "):
+            label = line[4:].strip()
+        elif line.startswith("[//]: #"):
+            path = line[9:-1]
+        elif line.startswith("_"):
+            description = line[1:-1]
+        elif line.startswith("-"):
+            assumptions.append(line[2:])
+        else:
+            explanation = line
+
+            entries.append(
+                (
+                    path,
+                    label,
+                    create_fmt_string(description),
+                    create_fmt_string(explanation),
+                )
+            )
+
+# Add assumptions
+assert title == "Assumptions"
+print("    const assumptions = [")
+for assumption in assumptions:
+    print(f"        '{assumption}',")
+print("    ];")
+print()
+print("    return { groups: groups, assumptions: assumptions };")
+print("}")
+print("// END OF AUTOMATICALLY GENERATED CODE")
+print()
+print()
diff --git a/npm/ux/index.ts b/npm/ux/index.ts
index e877c0db..6506ea2f 100644
--- a/npm/ux/index.ts
+++ b/npm/ux/index.ts
@@ -5,7 +5,9 @@
 // to a CSS file adjacent to the JS bundle and with the same name.
 import "./qsharp-ux.css";
 
+export { CreateSingleEstimateResult, type ReData } from "./data.js";
 export { Histogram } from "./histogram.js";
-export { ReTable, type ReData } from "./reTable.js";
+export { ReTable } from "./reTable.js";
 export { SpaceChart } from "./spaceChart.js";
-export { ResultsTable } from "./resultsTable.js";
+export { ScatterChart } from "./scatterChart.js";
+export { EstimatesOverview } from "./estimatesOverview.js";
diff --git a/npm/ux/output_data.md b/npm/ux/output_data.md
new file mode 100644
index 00000000..5f9adf4f
--- /dev/null
+++ b/npm/ux/output_data.md
@@ -0,0 +1,537 @@
+---
+geometry:
+  - left=1cm
+  - right=1cm
+  - top=1cm
+  - bottom=2cm
+classoption:
+  - 10pt
+...
+
+# Output data
+
+In this document, we describe all output parameters which are printed by the Azure Quantum Resource Estimator. Second-level headings contain one group in the output that consists of a header and a table with resource estimation data. Each third-level heading corresponds to an entry, which is preceded by a short description text in italic and a long description text. The short description text will be always visible in the third column next to the value, whereas the long description text will be visible as a tooltip when hovering over the corresponding row in the table. The description texts may contain some variable names such as `variable` or $\mathtt{variable}$ in formulas. These will be substituted by the actual computed values extracted from the resource estimates.
+
+## Physical resource estimates
+
+### Runtime
+
+[//]: # "physicalCountsFormatted/runtime"
+
+_Total runtime_
+
+This is a runtime estimate for the execution time of the algorithm. In general, the execution time corresponds to the duration of one logical cycle (`logicalQubit/logicalCycleTime` nanosecs) multiplied by the `physicalCounts/breakdown/algorithmicLogicalDepth` logical cycles to run the algorithm. If however the duration of a single T factory (here: `tfactory/runtime` nanosecs) is larger than the algorithm runtime, we extend the number of logical cycles artificially in order to exceed the runtime of a single T factory.
+
+### rQOPS
+
+[//]: # "physicalCountsFormatted/rqops"
+
+_Reliable quantum operations per second_
+
+The value is computed as the number of logical qubits after layout (`physicalCounts/breakdown/algorithmicLogicalQubits`) (with a logical error rate of `physicalCountsFormatted/requiredLogicalQubitErrorRate`) multiplied by the clock frequency (`logicalQubit/logicalCyclesPerSecond`), which is the number of logical cycles per second.
+
+### Physical qubits
+
+[//]: # "physicalCountsFormatted/physicalQubits"
+
+_Number of physical qubits_
+
+This value represents the total number of physical qubits, which is the sum of `physicalCounts/breakdown/physicalQubitsForAlgorithm` physical qubits to implement the algorithm logic, and `physicalCounts/breakdown/physicalQubitsForTfactories` physical qubits to execute the T factories that are responsible to produce the T states that are consumed by the algorithm.
+
+## Resource estimates breakdown
+
+### Logical algorithmic qubits
+
+[//]: # "physicalCountsFormatted/algorithmicLogicalQubits"
+
+_Number of logical qubits for the algorithm after layout_
+
+Laying out the logical qubits in the presence of nearest-neighbor constraints requires additional logical qubits. In particular, to layout the $Q_{\rm alg} = \mathtt{logicalCounts/numQubits}$ logical qubits in the input algorithm, we require in total $2 \cdot Q_{\rm alg} + \lceil \sqrt{8 \cdot Q_{\rm alg}}\rceil + 1 = \mathtt{physicalCounts/breakdown/algorithmicLogicalQubits}$ logical qubits.
+
+### Algorithmic depth
+
+[//]: # "physicalCountsFormatted/algorithmicLogicalDepth"
+
+_Number of logical cycles for the algorithm_
+
+To execute the algorithm using _Parallel Synthesis Sequential Pauli Computation_ (PSSPC), operations are scheduled in terms of multi-qubit Pauli measurements, for which assume an execution time of one logical cycle. Based on the input algorithm, we require one multi-qubit measurement for the `logicalCounts/measurementCount` single-qubit measurements, the `logicalCounts/rotationCount` arbitrary single-qubit rotations, and the `logicalCounts/tCount` T gates, three multi-qubit measurements for each of the `logicalCounts/cczCount` CCZ and `logicalCounts/ccixCount` CCiX gates in the input program, as well as `physicalCountsFormatted/numTsPerRotation` multi-qubit measurements for each of the `logicalCounts/rotationDepth` non-Clifford layers in which there is at least one single-qubit rotation with an arbitrary angle rotation.
+
+### Logical depth
+
+[//]: # "physicalCountsFormatted/logicalDepth"
+
+_Number of logical cycles performed_
+
+This number is usually equal to the logical depth of the algorithm, which is `physicalCounts/breakdown/algorithmicLogicalDepth`. However, in the case in which a single T factory is slower than the execution time of the algorithm, we adjust the logical cycle depth to exceed the T factory's execution time.
+
+### Clock frequency
+
+[//]: # "physicalCountsFormatted/clockFrequency"
+
+_Number of logical cycles per second_
+
+This is the number of logical cycles that can be performed within one second. The logical cycle time is `physicalCountsFormatted/logicalCycleTime`.
+
+### Number of T states
+
+[//]: # "physicalCountsFormatted/numTstates"
+
+_Number of T states consumed by the algorithm_
+
+To execute the algorithm, we require one T state for each of the `logicalCounts/tCount` T gates, four T states for each of the `logicalCounts/cczCount` CCZ and `logicalCounts/ccixCount` CCiX gates, as well as `physicalCountsFormatted/numTsPerRotation` for each of the `logicalCounts/rotationCount` single-qubit rotation gates with arbitrary angle rotation.
+
+### Number of T factories
+
+[//]: # "physicalCountsFormatted/numTfactories"
+
+_Number of T factories capable of producing the demanded `physicalCounts/breakdown/numTstates` T states during the algorithm's runtime_
+
+The total number of T factories `physicalCounts/breakdown/numTfactories` that are executed in parallel is computed as $\left\lceil\dfrac{\text{T states}\cdot\text{T factory duration}}{\text{T states per T factory}\cdot\text{algorithm runtime}}\right\rceil = \left\lceil\dfrac{\mathtt{physicalCounts/breakdown/numTstates} \cdot \mathtt{tfactory/runtime}\;\text{ns}}{\mathtt{tfactory/numTstates} \cdot \mathtt{physicalCounts/runtime}\;\text{ns}}\right\rceil$
+
+### Number of T factory invocations
+
+[//]: # "physicalCountsFormatted/numTfactoryRuns"
+
+_Number of times all T factories are invoked_
+
+In order to prepare the `physicalCounts/breakdown/numTstates` T states, the `physicalCounts/breakdown/numTfactories` copies of the T factory are repeatedly invoked `physicalCounts/breakdown/numTfactoryRuns` times.
+
+### Physical algorithmic qubits
+
+[//]: # "physicalCountsFormatted/physicalQubitsForAlgorithm"
+
+_Number of physical qubits for the algorithm after layout_
+
+The `physicalCounts/breakdown/physicalQubitsForAlgorithm` are the product of the `physicalCounts/breakdown/algorithmicLogicalQubits` logical qubits after layout and the `logicalQubit/physicalQubits` physical qubits that encode a single logical qubit.
+
+### Physical T factory qubits
+
+[//]: # "physicalCountsFormatted/physicalQubitsForTfactories"
+
+_Number of physical qubits for the T factories_
+
+Each T factory requires `tfactory/physicalQubits` physical qubits and we run `physicalCounts/breakdown/numTfactories` in parallel, therefore we need $\mathtt{physicalCounts/breakdown/physicalQubitsForTfactories} = \mathtt{tfactory/physicalQubits} \cdot \mathtt{physicalCounts/breakdown/numTfactories}$ qubits.
+
+### Required logical qubit error rate
+
+[//]: # "physicalCountsFormatted/requiredLogicalQubitErrorRate"
+
+_The minimum logical qubit error rate required to run the algorithm within the error budget_
+
+The minimum logical qubit error rate is obtained by dividing the logical error probability `physicalCountsFormatted/errorBudgetLogical` by the product of `physicalCounts/breakdown/algorithmicLogicalQubits` logical qubits and the total cycle count `physicalCounts/breakdown/logicalDepth`.
+
+### Required logical T state error rate
+
+[//]: # "physicalCountsFormatted/requiredLogicalTstateErrorRate"
+
+_The minimum T state error rate required for distilled T states_
+
+The minimum T state error rate is obtained by dividing the T distillation error probability `physicalCountsFormatted/errorBudgetTstates` by the total number of T states `physicalCounts/breakdown/numTstates`.
+
+### Number of T states per rotation
+
+[//]: # "physicalCountsFormatted/numTsPerRotation"
+
+_Number of T states to implement a rotation with an arbitrary angle_
+
+The number of T states to implement a rotation with an arbitrary angle is $\lceil 0.53 \log_2(\mathtt{logicalCounts/rotationCount} / \mathtt{errorBudget/rotations}) + 5.3\rceil$ [[arXiv:2203.10064](https://arxiv.org/abs/2203.10064)]. For simplicity, we use this formula for all single-qubit arbitrary angle rotations, and do not distinguish between best, worst, and average cases.
+
+## Logical qubit parameters
+
+### QEC scheme
+
+[//]: # "jobParams/qecScheme/name"
+
+_Name of QEC scheme_
+
+You can load pre-defined QEC schemes by using the name `surface_code` or `floquet_code`. The latter only works with Majorana qubits.
+
+### Code distance
+
+[//]: # "logicalQubit/codeDistance"
+
+_Required code distance for error correction_
+
+The code distance is the smallest odd integer greater or equal to $\dfrac{2\log(\mathtt{jobParams/qecScheme/crossingPrefactor} / \mathtt{physicalCounts/breakdown/requiredLogicalQubitErrorRate})}{\log(\mathtt{jobParams/qecScheme/errorCorrectionThreshold}/\mathtt{physicalCounts/breakdown/cliffordErrorRate})} - 1$
+
+### Physical qubits
+
+[//]: # "physicalCountsFormatted/physicalQubitsPerLogicalQubit"
+
+_Number of physical qubits per logical qubit_
+
+The number of physical qubits per logical qubit are evaluated using the formula `jobParams/qecScheme/physicalQubitsPerLogicalQubit` that can be user-specified.
+
+### Logical cycle time
+
+[//]: # "physicalCountsFormatted/logicalCycleTime"
+
+_Duration of a logical cycle in nanoseconds_
+
+The runtime of one logical cycle in nanoseconds is evaluated using the formula `jobParams/qecScheme/logicalCycleTime` that can be user-specified.
+
+### Logical qubit error rate
+
+[//]: # "physicalCountsFormatted/logicalErrorRate"
+
+_Logical qubit error rate_
+
+The logical qubit error rate is computed as $\mathtt{jobParams/qecScheme/crossingPrefactor} \cdot \left(\dfrac{\mathtt{physicalCounts/breakdown/cliffordErrorRate}}{\mathtt{jobParams/qecScheme/errorCorrectionThreshold}}\right)^\frac{\mathtt{logicalQubit/codeDistance} + 1}{2}$
+
+### Crossing prefactor
+
+[//]: # "jobParams/qecScheme/crossingPrefactor"
+
+_Crossing prefactor used in QEC scheme_
+
+The crossing prefactor is usually extracted numerically from simulations when fitting an exponential curve to model the relationship between logical and physical error rate.
+
+### Error correction threshold
+
+[//]: # "jobParams/qecScheme/errorCorrectionThreshold"
+
+_Error correction threshold used in QEC scheme_
+
+The error correction threshold is the physical error rate below which the error rate of the logical qubit is less than the error rate of the physical qubit that constitute it. This value is usually extracted numerically from simulations of the logical error rate.
+
+### Logical cycle time formula
+
+[//]: # "jobParams/qecScheme/logicalCycleTime"
+
+_QEC scheme formula used to compute logical cycle time_
+
+This is the formula that is used to compute the logical cycle time `logicalQubit/logicalCycleTime` ns.
+
+### Physical qubits formula
+
+[//]: # "jobParams/qecScheme/physicalQubitsPerLogicalQubit"
+
+_QEC scheme formula used to compute number of physical qubits per logical qubit_
+
+This is the formula that is used to compute the number of physical qubits per logical qubits `logicalQubit/physicalQubits`.
+
+## T factory parameters
+
+### Physical qubits
+
+[//]: # "physicalCountsFormatted/tfactoryPhysicalQubits"
+
+_Number of physical qubits for a single T factory_
+
+This corresponds to the maximum number of physical qubits over all rounds of T distillation units in a T factory. A round of distillation contains of multiple copies of distillation units to achieve the required success probability of producing a T state with the expected logical T state error rate.
+
+### Runtime
+
+[//]: # "physicalCountsFormatted/tfactoryRuntime"
+
+_Runtime of a single T factory_
+
+The runtime of a single T factory is the accumulated runtime of executing each round in a T factory.
+
+### Number of output T states per run
+
+[//]: # "tfactory/numTstates"
+
+_Number of output T states produced in a single run of T factory_
+
+The T factory takes as input `tfactory/numInputTstates` noisy physical T states with an error rate of `jobParams/qubitParams/tGateErrorRate` and produces `tfactory/numTstates` T states with an error rate of `physicalCountsFormatted/tstateLogicalErrorRate`.
+
+### Number of input T states per run
+
+[//]: # "physicalCountsFormatted/numInputTstates"
+
+_Number of physical input T states consumed in a single run of a T factory_
+
+This value includes the physical input T states of all copies of the distillation unit in the first round.
+
+### Distillation rounds
+
+[//]: # "tfactory/numRounds"
+
+_The number of distillation rounds_
+
+This is the number of distillation rounds. In each round one or multiple copies of some distillation unit is executed.
+
+### Distillation units per round
+
+[//]: # "physicalCountsFormatted/numUnitsPerRound"
+
+_The number of units in each round of distillation_
+
+This is the number of copies for the distillation units per round.
+
+### Distillation units
+
+[//]: # "physicalCountsFormatted/unitNamePerRound"
+
+_The types of distillation units_
+
+These are the types of distillation units that are executed in each round. The units can be either physical or logical, depending on what type of qubit they are operating. Space-efficient units require fewer qubits for the cost of longer runtime compared to Reed-Muller preparation units.
+
+### Distillation code distances
+
+[//]: # "physicalCountsFormatted/codeDistancePerRound"
+
+_The code distance in each round of distillation_
+
+This is the code distance used for the units in each round. If the code distance is 1, then the distillation unit operates on physical qubits instead of error-corrected logical qubits.
+
+### Number of physical qubits per round
+
+[//]: # "physicalCountsFormatted/physicalQubitsPerRound"
+
+_The number of physical qubits used in each round of distillation_
+
+The maximum number of physical qubits over all rounds is the number of physical qubits for the T factory, since qubits are reused by different rounds.
+
+### Runtime per round
+
+[//]: # "physicalCountsFormatted/tfactoryRuntimePerRound"
+
+_The runtime of each distillation round_
+
+The runtime of the T factory is the sum of the runtimes in all rounds.
+
+### Logical T state error rate
+
+[//]: # "physicalCountsFormatted/tstateLogicalErrorRate"
+
+_Logical T state error rate_
+
+This is the logical T state error rate achieved by the T factory which is equal or smaller than the required error rate `physicalCountsFormatted/requiredLogicalTstateErrorRate`.
+
+## Pre-layout logical resources
+
+### Logical qubits (pre-layout)
+
+[//]: # "physicalCountsFormatted/logicalCountsNumQubits"
+
+_Number of logical qubits in the input quantum program_
+
+We determine `physicalCounts/breakdown/algorithmicLogicalQubits` algorithmic logical qubits from this number by assuming to align them in a 2D grid. Auxiliary qubits are added to allow for sufficient space to execute multi-qubit Pauli measurements on all or a subset of the logical qubits.
+
+### T gates
+
+[//]: # "physicalCountsFormatted/logicalCountsTCount"
+
+_Number of T gates in the input quantum program_
+
+This includes all T gates and adjoint T gates, but not T gates used to implement rotation gates with arbitrary angle, CCZ gates, or CCiX gates.
+
+### Rotation gates
+
+[//]: # "physicalCountsFormatted/logicalCountsRotationCount"
+
+_Number of rotation gates in the input quantum program_
+
+This is the number of all rotation gates. If an angle corresponds to a Pauli, Clifford, or T gate, it is not accounted for in this number.
+
+### Rotation depth
+
+[//]: # "physicalCountsFormatted/logicalCountsRotationDepth"
+
+_Depth of rotation gates in the input quantum program_
+
+This is the number of all non-Clifford layers that include at least one single-qubit rotation gate with an arbitrary angle.
+
+### CCZ gates
+
+[//]: # "physicalCountsFormatted/logicalCountsCczCount"
+
+_Number of CCZ-gates in the input quantum program_
+
+This is the number of CCZ gates.
+
+### CCiX gates
+
+[//]: # "physicalCountsFormatted/logicalCountsCcixCount"
+
+_Number of CCiX-gates in the input quantum program_
+
+This is the number of CCiX gates, which applies $-iX$ controlled on two control qubits [[1212.5069](https://arxiv.org/abs/1212.5069)].
+
+### Measurement operations
+
+[//]: # "physicalCountsFormatted/logicalCountsMeasurementCount"
+
+_Number of single qubit measurements in the input quantum program_
+
+This is the number of single qubit measurements in Pauli basis that are used in the input program. Note that all measurements are counted, however, the measurement result is is determined randomly (with a fixed seed) to be 0 or 1 with a probability of 50%.
+
+## Assumed error budget
+
+### Total error budget
+
+[//]: # "physicalCountsFormatted/errorBudget"
+
+_Total error budget for the algorithm_
+
+The total error budget sets the overall allowed error for the algorithm, i.e., the number of times it is allowed to fail. Its value must be between 0 and 1 and the default value is 0.001, which corresponds to 0.1%, and means that the algorithm is allowed to fail once in 1000 executions. This parameter is highly application specific. For example, if one is running Shor's algorithm for factoring integers, a large value for the error budget may be tolerated as one can check that the output are indeed the prime factors of the input. On the other hand, a much smaller error budget may be needed for an algorithm solving a problem with a solution which cannot be efficiently verified. This budget $\epsilon = \epsilon_{\log} + \epsilon_{\rm dis} + \epsilon_{\rm syn}$ is uniformly distributed and applies to errors $\epsilon_{\log}$ to implement logical qubits, an error budget $\epsilon_{\rm dis}$ to produce T states through distillation, and an error budget $\epsilon_{\rm syn}$ to synthesize rotation gates with arbitrary angles. Note that for distillation and rotation synthesis, the respective error budgets $\epsilon_{\rm dis}$ and $\epsilon_{\rm syn}$ are uniformly distributed among all T states and all rotation gates, respectively. If there are no rotation gates in the input algorithm, the error budget is uniformly distributed to logical errors and T state errors.
+
+### Logical error probability
+
+[//]: # "physicalCountsFormatted/errorBudgetLogical"
+
+_Probability of at least one logical error_
+
+This is one third of the total error budget `physicalCountsFormatted/errorBudget` if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise.
+
+### T distillation error probability
+
+[//]: # "physicalCountsFormatted/errorBudgetTstates"
+
+_Probability of at least one faulty T distillation_
+
+This is one third of the total error budget `physicalCountsFormatted/errorBudget` if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise.
+
+### Rotation synthesis error probability
+
+[//]: # "physicalCountsFormatted/errorBudgetRotations"
+
+_Probability of at least one failed rotation synthesis_
+
+This is one third of the total error budget `physicalCountsFormatted/errorBudget`.
+
+## Physical qubit parameters
+
+_Info:_ Note that not all values are shown depending on the instruction set of the qubit parameters.
+
+### Qubit name
+
+[//]: # "jobParams/qubitParams/name"
+
+_Some descriptive name for the qubit model_
+
+You can load pre-defined qubit parameters by using the names `qubit_gate_ns_e3`, `qubit_gate_ns_e4`, `qubit_gate_us_e3`, `qubit_gate_us_e4`, `qubit_maj_ns_e4`, or `qubit_maj_ns_e6`. The names of these pre-defined qubit parameters indicate the instruction set (gate-based or Majorana), the operation speed (ns or µs regime), as well as the fidelity (e.g., e3 for $10^{-3}$ gate error rates).
+
+### Instruction set
+
+[//]: # "jobParams/qubitParams/instructionSet"
+
+_Underlying qubit technology (gate-based or Majorana)_
+
+When modeling the physical qubit abstractions, we distinguish between two different physical instruction sets that are used to operate the qubits. The physical instruction set can be either _gate-based_ or _Majorana_. A gate-based instruction set provides single-qubit measurement, single-qubit gates (incl. T gates), and two-qubit gates. A Majorana instruction set provides a physical T gate, single-qubit measurement and two-qubit joint measurement operations.
+
+### Single-qubit measurement time
+
+[//]: # "jobParams/qubitParams/oneQubitMeasurementTime"
+
+_Operation time for single-qubit measurement (t_meas) in ns_
+
+This is the operation time in nanoseconds to perform a single-qubit measurement in the Pauli basis.
+
+### Two-qubit measurement time
+
+[//]: # "jobParams/qubitParams/twoQubitJointMeasurementTime"
+
+_Operation time for two-qubit measurement in ns_
+
+This is the operation time in nanoseconds to perform a non-destructive two-qubit joint Pauli measurement.
+
+### Single-qubit gate time
+
+[//]: # "jobParams/qubitParams/oneQubitGateTime"
+
+_Operation time for single-qubit gate (t_gate) in ns_
+
+This is the operation time in nanoseconds to perform a single-qubit Clifford operation, e.g., Hadamard or Phase gates.
+
+### Two-qubit gate time
+
+[//]: # "jobParams/qubitParams/twoQubitGateTime"
+
+_Operation time for two-qubit gate in ns_
+
+This is the operation time in nanoseconds to perform a two-qubit Clifford operation, e.g., a CNOT or CZ gate.
+
+### T gate time
+
+[//]: # "jobParams/qubitParams/tGateTime"
+
+_Operation time for a T gate_
+
+This is the operation time in nanoseconds to execute a T gate.
+
+### Single-qubit measurement error rate
+
+[//]: # "jobParams/qubitParams/oneQubitMeasurementErrorRate"
+
+_Error rate for single-qubit measurement_
+
+This is the probability in which a single-qubit measurement in the Pauli basis may fail.
+
+### Two-qubit measurement error rate
+
+[//]: # "jobParams/qubitParams/twoQubitJointMeasurementErrorRate"
+
+_Error rate for two-qubit measurement_
+
+This is the probability in which a non-destructive two-qubit joint Pauli measurement may fail.
+
+### Single-qubit error rate
+
+[//]: # "jobParams/qubitParams/oneQubitGateErrorRate"
+
+_Error rate for single-qubit Clifford gate (p)_
+
+This is the probability in which a single-qubit Clifford operation, e.g., Hadamard or Phase gates, may fail.
+
+### Two-qubit error rate
+
+[//]: # "jobParams/qubitParams/twoQubitGateErrorRate"
+
+_Error rate for two-qubit Clifford gate_
+
+This is the probability in which a two-qubit Clifford operation, e.g., CNOT or CZ gates, may fail.
+
+### T gate error rate
+
+[//]: # "jobParams/qubitParams/tGateErrorRate"
+
+_Error rate to prepare single-qubit T state or apply a T gate (p_T)_
+
+This is the probability in which executing a single T gate may fail.
+
+## Constraints
+
+### Logical depth factor
+
+[//]: # "physicalCountsFormatted/logicalDepthFactor"
+
+_Factor, the initial number of logical cycles is multiplied by_
+
+This is the factor takes into account a potential overhead to the initial number of logical cycles.
+
+### Maximum number of T factories
+
+[//]: # "physicalCountsFormatted/maxTFactories"
+
+_The maximum number of T factories can be utilized during the algorithm's runtime_
+
+This is the maximum number of T factories used for producing the demanded T states, which can be created and executed by the algorithm in parallel.
+
+### Maximum runtime duration
+
+[//]: # "physicalCountsFormatted/maxDuration"
+
+_The maximum runtime duration allowed for the algorithm runtime_
+
+This is the maximum time allowed to the algorithm. If specified, the estimator targets to minimize the number of physical qubits consumed by the algorithm for runtimes under the maximum allowed.
+
+### Maximum number of physical qubits
+
+[//]: # "physicalCountsFormatted/maxPhysicalQubits"
+
+_The maximum number of physical qubits allowed for utilization to the algorithm_
+
+This is the maximum number of physical qubits available to the algorithm. If specified, the estimator targets to minimize the runtime of the algorithm with number of physical qubits consumed not exceeding this maximum.
+
+## Assumptions
+
+- _More details on the following lists of assumptions can be found in the paper [Accessing requirements for scaling quantum computers and their applications](https://aka.ms/AQ/RE/Paper)._
+- **Uniform independent physical noise.** We assume that the noise on physical qubits and physical qubit operations is the standard circuit noise model. In particular we assume error events at different space-time locations are independent and that error rates are uniform across the system in time and space.
+- **Efficient classical computation.** We assume that classical overhead (compilation, control, feedback, readout, decoding, etc.) does not dominate the overall cost of implementing the full quantum algorithm.
+- **Extraction circuits for planar quantum ISA.** We assume that stabilizer extraction circuits with similar depth and error correction performance to those for standard surface and Hastings-Haah code patches can be constructed to implement all operations of the planar quantum ISA (instruction set architecture).
+- **Uniform independent logical noise.** We assume that the error rate of a logical operation is approximately equal to its space-time volume (the number of tiles multiplied by the number of logical time steps) multiplied by the error rate of a logical qubit in a standard one-tile patch in one logical time step.
+- **Negligible Clifford costs for synthesis.** We assume that the space overhead for synthesis and space and time overhead for transport of magic states within magic state factories and to synthesis qubits are all negligible.
+- **Smooth magic state consumption rate.** We assume that the rate of T state consumption throughout the compiled algorithm is almost constant, or can be made almost constant without significantly increasing the number of logical time steps for the algorithm.
diff --git a/npm/ux/qsharp-ux.css b/npm/ux/qsharp-ux.css
index cc7ea25e..5e0fe6bb 100644
--- a/npm/ux/qsharp-ux.css
+++ b/npm/ux/qsharp-ux.css
@@ -615,3 +615,38 @@ html {
   fill: var(--vscode-icon-foreground, gray);
   animation: codicon-spin 1.5s steps(45) infinite;
 }
+
+.qs-scatterChart {
+  font-size: 14px;
+}
+
+.qs-scatterChart-x-axisTitle,
+.qs-scatterChart-y-axisTitle {
+  text-anchor: middle;
+  font-size: 16px;
+  fill: var(--main-color);
+}
+
+.qs-scatterChart-y-axisTitle {
+  writing-mode: vertical-lr;
+  transform: rotate(180deg);
+}
+
+.qs-scatterChart-line {
+  stroke: var(--border-color);
+}
+
+.qs-scatterChart-point {
+  r: 2px;
+  stroke-width: 4px;
+}
+
+.qs-scatterChart-point:hover {
+  r: 3px;
+  stroke-width: 6px;
+}
+
+.qs-scatterChart-watermark {
+  font-size: 16px;
+  fill: var(--main-color);
+}
diff --git a/npm/ux/reTable.tsx b/npm/ux/reTable.tsx
index afbdd97c..fa6596bd 100644
--- a/npm/ux/reTable.tsx
+++ b/npm/ux/reTable.tsx
@@ -2,41 +2,20 @@
 // Licensed under the MIT License.
 
 import { useState } from "preact/hooks";
-
-export type ReData = {
-  status: string;
-  jobParams: any;
-  physicalCounts: any;
-  physicalCountsFormatted: any;
-  logicalQubit: any;
-  tfactory: any;
-  errorBudget: any;
-  logicalCounts: any;
-  reportData: {
-    groups: {
-      title: string;
-      alwaysVisible: boolean;
-      entries: {
-        path: string;
-        label: string;
-        description: string;
-        explanation: string;
-      }[];
-    }[];
-    assumptions: string[];
-  };
-  new: boolean;
-};
+import { SingleEstimateResult } from "./data.js";
+import { CreateReport } from "./report.js";
 
 export function ReTable(props: {
   mdRenderer: (input: string) => string;
-  estimatesData: ReData;
+  estimatesData: SingleEstimateResult;
 }) {
   const [showDetail, setShowDetail] = useState(false);
   const toggleDetail = () => {
     setShowDetail(!showDetail);
   };
 
+  const reportData = CreateReport(props.estimatesData);
+
   return (
     <div>
       <div>
@@ -48,7 +27,7 @@ export function ReTable(props: {
         />
         <label htmlFor="showDetail"> Show detailed rows</label>
       </div>
-      {props.estimatesData.reportData.groups.map((group) => {
+      {reportData.groups.map((group) => {
         return (
           <details className="estimate-details">
             <summary>
@@ -101,7 +80,7 @@ export function ReTable(props: {
           <strong>Assumptions</strong>
         </summary>
         <ul className="estimate-table">
-          {props.estimatesData.reportData.assumptions.map((assumption) => (
+          {reportData.assumptions.map((assumption) => (
             <li
               className="estimate-assumption"
               dangerouslySetInnerHTML={{ __html: props.mdRenderer(assumption) }}
diff --git a/npm/ux/report.ts b/npm/ux/report.ts
new file mode 100644
index 00000000..392df0b9
--- /dev/null
+++ b/npm/ux/report.ts
@@ -0,0 +1,591 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { SingleEstimateResult } from "./data.js";
+
+type ReportData = {
+  groups: ReportGroup[];
+  assumptions: string[];
+};
+
+type ReportGroup = {
+  title: string;
+  alwaysVisible: boolean;
+  entries: ReportEntry[];
+};
+
+type ReportEntry = {
+  path: string;
+  label: string;
+  description: string;
+  explanation: string;
+};
+
+// THIS CODE HAS BEEN AUTOMATICALLY GENERATED WITH generate_report_code.py from output_data.md
+export function CreateReport(result: SingleEstimateResult): ReportData {
+  const groups = [] as ReportGroup[];
+  let entries = [] as ReportEntry[];
+  const numberFormat = new Intl.NumberFormat();
+  const numberFormatF64 = new Intl.NumberFormat(undefined, {
+    maximumFractionDigits: 2,
+    minimumFractionDigits: 2,
+  });
+
+  entries = [];
+  entries.push({
+    path: "physicalCountsFormatted/runtime",
+    label: "Runtime",
+    description: `Total runtime`,
+    explanation: `This is a runtime estimate for the execution time of the algorithm. In general, the execution time corresponds to the duration of one logical cycle (${numberFormat.format(
+      result.logicalQubit.logicalCycleTime,
+    )} nanosecs) multiplied by the ${numberFormat.format(
+      result.physicalCountsFormatted.algorithmicLogicalDepth,
+    )} logical cycles to run the algorithm. If however the duration of a single T factory (here: ${numberFormat.format(
+      result.tfactory == null ? 0 : result.tfactory.runtime,
+    )} nanosecs) is larger than the algorithm runtime, we extend the number of logical cycles artificially in order to exceed the runtime of a single T factory.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/rqops",
+    label: "rQOPS",
+    description: `Reliable quantum operations per second`,
+    explanation: `The value is computed as the number of logical qubits after layout (${numberFormat.format(
+      result.physicalCounts.breakdown.algorithmicLogicalQubits,
+    )}) (with a logical error rate of ${
+      result.physicalCountsFormatted.requiredLogicalQubitErrorRate
+    }) multiplied by the clock frequency (${numberFormatF64.format(
+      result.physicalCounts.breakdown.clockFrequency,
+    )}), which is the number of logical cycles per second.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/physicalQubits",
+    label: "Physical qubits",
+    description: `Number of physical qubits`,
+    explanation: `This value represents the total number of physical qubits, which is the sum of ${numberFormat.format(
+      result.physicalCounts.breakdown.physicalQubitsForAlgorithm,
+    )} physical qubits to implement the algorithm logic, and ${numberFormat.format(
+      result.physicalCounts.breakdown.physicalQubitsForTfactories,
+    )} physical qubits to execute the T factories that are responsible to produce the T states that are consumed by the algorithm.`,
+  });
+  groups.push({
+    title: "Physical resource estimates",
+    alwaysVisible: true,
+    entries: entries,
+  });
+
+  entries = [];
+  entries.push({
+    path: "physicalCountsFormatted/algorithmicLogicalQubits",
+    label: "Logical algorithmic qubits",
+    description: `Number of logical qubits for the algorithm after layout`,
+    explanation: `Laying out the logical qubits in the presence of nearest-neighbor constraints requires additional logical qubits. In particular, to layout the $Q_{\\rm alg} = ${numberFormat.format(
+      result.logicalCounts.numQubits,
+    )}$ logical qubits in the input algorithm, we require in total $2 \\cdot Q_{\\rm alg} + \\lceil \\sqrt{8 \\cdot Q_{\\rm alg}}\\rceil + 1 = ${numberFormat.format(
+      result.physicalCounts.breakdown.algorithmicLogicalQubits,
+    )}$ logical qubits.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/algorithmicLogicalDepth",
+    label: "Algorithmic depth",
+    description: `Number of logical cycles for the algorithm`,
+    explanation: `To execute the algorithm using _Parallel Synthesis Sequential Pauli Computation_ (PSSPC), operations are scheduled in terms of multi-qubit Pauli measurements, for which assume an execution time of one logical cycle. Based on the input algorithm, we require one multi-qubit measurement for the ${numberFormat.format(
+      result.logicalCounts.measurementCount,
+    )} single-qubit measurements, the ${numberFormat.format(
+      result.logicalCounts.rotationCount,
+    )} arbitrary single-qubit rotations, and the ${numberFormat.format(
+      result.logicalCounts.tCount,
+    )} T gates, three multi-qubit measurements for each of the ${numberFormat.format(
+      result.logicalCounts.cczCount,
+    )} CCZ and ${numberFormat.format(
+      result.logicalCounts.ccixCount,
+    )} CCiX gates in the input program, as well as ${
+      result.physicalCountsFormatted.numTsPerRotation
+    } multi-qubit measurements for each of the ${numberFormat.format(
+      result.logicalCounts.rotationDepth,
+    )} non-Clifford layers in which there is at least one single-qubit rotation with an arbitrary angle rotation.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalDepth",
+    label: "Logical depth",
+    description: `Number of logical cycles performed`,
+    explanation: `This number is usually equal to the logical depth of the algorithm, which is ${numberFormat.format(
+      result.physicalCountsFormatted.algorithmicLogicalDepth,
+    )}. However, in the case in which a single T factory is slower than the execution time of the algorithm, we adjust the logical cycle depth to exceed the T factory's execution time.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/clockFrequency",
+    label: "Clock frequency",
+    description: `Number of logical cycles per second`,
+    explanation: `This is the number of logical cycles that can be performed within one second. The logical cycle time is ${result.physicalCountsFormatted.logicalCycleTime}.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/numTstates",
+    label: "Number of T states",
+    description: `Number of T states consumed by the algorithm`,
+    explanation: `To execute the algorithm, we require one T state for each of the ${numberFormat.format(
+      result.logicalCounts.tCount,
+    )} T gates, four T states for each of the ${numberFormat.format(
+      result.logicalCounts.cczCount,
+    )} CCZ and ${numberFormat.format(
+      result.logicalCounts.ccixCount,
+    )} CCiX gates, as well as ${
+      result.physicalCountsFormatted.numTsPerRotation
+    } for each of the ${numberFormat.format(
+      result.logicalCounts.rotationCount,
+    )} single-qubit rotation gates with arbitrary angle rotation.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/numTfactories",
+    label: "Number of T factories",
+    description: `Number of T factories capable of producing the demanded ${numberFormat.format(
+      result.physicalCounts.breakdown.numTstates,
+    )} T states during the algorithm's runtime`,
+    explanation: `The total number of T factories ${numberFormat.format(
+      result.physicalCounts.breakdown.numTfactories,
+    )} that are executed in parallel is computed as $\\left\\lceil\\dfrac{\\text{T states}\\cdot\\text{T factory duration}}{\\text{T states per T factory}\\cdot\\text{algorithm runtime}}\\right\\rceil = \\left\\lceil\\dfrac{${numberFormat.format(
+      result.physicalCounts.breakdown.numTstates,
+    )} \\cdot ${numberFormat.format(
+      result.tfactory == null ? 0 : result.tfactory.runtime,
+    )}\\;\\text{ns}}{${numberFormat.format(
+      result.tfactory == null ? 0 : result.tfactory.numTstates,
+    )} \\cdot ${numberFormat.format(
+      result.physicalCounts.runtime,
+    )}\\;\\text{ns}}\\right\\rceil$`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/numTfactoryRuns",
+    label: "Number of T factory invocations",
+    description: `Number of times all T factories are invoked`,
+    explanation: `In order to prepare the ${numberFormat.format(
+      result.physicalCounts.breakdown.numTstates,
+    )} T states, the ${numberFormat.format(
+      result.physicalCounts.breakdown.numTfactories,
+    )} copies of the T factory are repeatedly invoked ${numberFormat.format(
+      result.physicalCounts.breakdown.numTfactoryRuns,
+    )} times.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/physicalQubitsForAlgorithm",
+    label: "Physical algorithmic qubits",
+    description: `Number of physical qubits for the algorithm after layout`,
+    explanation: `The ${numberFormat.format(
+      result.physicalCounts.breakdown.physicalQubitsForAlgorithm,
+    )} are the product of the ${numberFormat.format(
+      result.physicalCounts.breakdown.algorithmicLogicalQubits,
+    )} logical qubits after layout and the ${numberFormat.format(
+      result.logicalQubit.physicalQubits,
+    )} physical qubits that encode a single logical qubit.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/physicalQubitsForTfactories",
+    label: "Physical T factory qubits",
+    description: `Number of physical qubits for the T factories`,
+    explanation: `Each T factory requires ${numberFormat.format(
+      result.tfactory == null ? 0 : result.tfactory.physicalQubits,
+    )} physical qubits and we run ${numberFormat.format(
+      result.physicalCounts.breakdown.numTfactories,
+    )} in parallel, therefore we need $${numberFormat.format(
+      result.physicalCounts.breakdown.physicalQubitsForTfactories,
+    )} = ${numberFormat.format(
+      result.tfactory == null ? 0 : result.tfactory.physicalQubits,
+    )} \\cdot ${numberFormat.format(
+      result.physicalCounts.breakdown.numTfactories,
+    )}$ qubits.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/requiredLogicalQubitErrorRate",
+    label: "Required logical qubit error rate",
+    description: `The minimum logical qubit error rate required to run the algorithm within the error budget`,
+    explanation: `The minimum logical qubit error rate is obtained by dividing the logical error probability ${
+      result.physicalCountsFormatted.errorBudgetLogical
+    } by the product of ${numberFormat.format(
+      result.physicalCounts.breakdown.algorithmicLogicalQubits,
+    )} logical qubits and the total cycle count ${numberFormat.format(
+      result.physicalCounts.breakdown.logicalDepth,
+    )}.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/requiredLogicalTstateErrorRate",
+    label: "Required logical T state error rate",
+    description: `The minimum T state error rate required for distilled T states`,
+    explanation: `The minimum T state error rate is obtained by dividing the T distillation error probability ${
+      result.physicalCountsFormatted.errorBudgetTstates
+    } by the total number of T states ${numberFormat.format(
+      result.physicalCounts.breakdown.numTstates,
+    )}.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/numTsPerRotation",
+    label: "Number of T states per rotation",
+    description: `Number of T states to implement a rotation with an arbitrary angle`,
+    explanation: `The number of T states to implement a rotation with an arbitrary angle is $\\lceil 0.53 \\log_2(${numberFormat.format(
+      result.logicalCounts.rotationCount,
+    )} / ${
+      result.errorBudget.rotations
+    }) + 5.3\\rceil$ [[arXiv:2203.10064](https://arxiv.org/abs/2203.10064)]. For simplicity, we use this formula for all single-qubit arbitrary angle rotations, and do not distinguish between best, worst, and average cases.`,
+  });
+  groups.push({
+    title: "Resource estimates breakdown",
+    alwaysVisible: false,
+    entries: entries,
+  });
+
+  entries = [];
+  entries.push({
+    path: "jobParams/qecScheme/name",
+    label: "QEC scheme",
+    description: `Name of QEC scheme`,
+    explanation: `You can load pre-defined QEC schemes by using the name \`surface_code\` or \`floquet_code\`. The latter only works with Majorana qubits.`,
+  });
+  entries.push({
+    path: "logicalQubit/codeDistance",
+    label: "Code distance",
+    description: `Required code distance for error correction`,
+    explanation: `The code distance is the smallest odd integer greater or equal to $\\dfrac{2\\log(${result.jobParams.qecScheme.crossingPrefactor} / ${result.physicalCounts.breakdown.requiredLogicalQubitErrorRate})}{\\log(${result.jobParams.qecScheme.errorCorrectionThreshold}/${result.physicalCounts.breakdown.cliffordErrorRate})} - 1$`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/physicalQubitsPerLogicalQubit",
+    label: "Physical qubits",
+    description: `Number of physical qubits per logical qubit`,
+    explanation: `The number of physical qubits per logical qubit are evaluated using the formula ${result.jobParams.qecScheme.physicalQubitsPerLogicalQubit} that can be user-specified.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCycleTime",
+    label: "Logical cycle time",
+    description: `Duration of a logical cycle in nanoseconds`,
+    explanation: `The runtime of one logical cycle in nanoseconds is evaluated using the formula ${result.jobParams.qecScheme.logicalCycleTime} that can be user-specified.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalErrorRate",
+    label: "Logical qubit error rate",
+    description: `Logical qubit error rate`,
+    explanation: `The logical qubit error rate is computed as $${result.jobParams.qecScheme.crossingPrefactor} \\cdot \\left(\\dfrac{${result.physicalCounts.breakdown.cliffordErrorRate}}{${result.jobParams.qecScheme.errorCorrectionThreshold}}\\right)^\\frac{${result.logicalQubit.codeDistance} + 1}{2}$`,
+  });
+  entries.push({
+    path: "jobParams/qecScheme/crossingPrefactor",
+    label: "Crossing prefactor",
+    description: `Crossing prefactor used in QEC scheme`,
+    explanation: `The crossing prefactor is usually extracted numerically from simulations when fitting an exponential curve to model the relationship between logical and physical error rate.`,
+  });
+  entries.push({
+    path: "jobParams/qecScheme/errorCorrectionThreshold",
+    label: "Error correction threshold",
+    description: `Error correction threshold used in QEC scheme`,
+    explanation: `The error correction threshold is the physical error rate below which the error rate of the logical qubit is less than the error rate of the physical qubit that constitute it. This value is usually extracted numerically from simulations of the logical error rate.`,
+  });
+  entries.push({
+    path: "jobParams/qecScheme/logicalCycleTime",
+    label: "Logical cycle time formula",
+    description: `QEC scheme formula used to compute logical cycle time`,
+    explanation: `This is the formula that is used to compute the logical cycle time ${numberFormat.format(
+      result.logicalQubit.logicalCycleTime,
+    )} ns.`,
+  });
+  entries.push({
+    path: "jobParams/qecScheme/physicalQubitsPerLogicalQubit",
+    label: "Physical qubits formula",
+    description: `QEC scheme formula used to compute number of physical qubits per logical qubit`,
+    explanation: `This is the formula that is used to compute the number of physical qubits per logical qubits ${numberFormat.format(
+      result.logicalQubit.physicalQubits,
+    )}.`,
+  });
+  groups.push({
+    title: "Logical qubit parameters",
+    alwaysVisible: false,
+    entries: entries,
+  });
+
+  if (result.tfactory != null) {
+    entries = [];
+    entries.push({
+      path: "physicalCountsFormatted/tfactoryPhysicalQubits",
+      label: "Physical qubits",
+      description: `Number of physical qubits for a single T factory`,
+      explanation: `This corresponds to the maximum number of physical qubits over all rounds of T distillation units in a T factory. A round of distillation contains of multiple copies of distillation units to achieve the required success probability of producing a T state with the expected logical T state error rate.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/tfactoryRuntime",
+      label: "Runtime",
+      description: `Runtime of a single T factory`,
+      explanation: `The runtime of a single T factory is the accumulated runtime of executing each round in a T factory.`,
+    });
+    entries.push({
+      path: "tfactory/numTstates",
+      label: "Number of output T states per run",
+      description: `Number of output T states produced in a single run of T factory`,
+      explanation: `The T factory takes as input ${numberFormat.format(
+        result.tfactory == null ? 0 : result.tfactory.numInputTstates,
+      )} noisy physical T states with an error rate of ${
+        result.jobParams.qubitParams.tGateErrorRate
+      } and produces ${numberFormat.format(
+        result.tfactory == null ? 0 : result.tfactory.numTstates,
+      )} T states with an error rate of ${
+        result.physicalCountsFormatted.tstateLogicalErrorRate
+      }.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/numInputTstates",
+      label: "Number of input T states per run",
+      description: `Number of physical input T states consumed in a single run of a T factory`,
+      explanation: `This value includes the physical input T states of all copies of the distillation unit in the first round.`,
+    });
+    entries.push({
+      path: "tfactory/numRounds",
+      label: "Distillation rounds",
+      description: `The number of distillation rounds`,
+      explanation: `This is the number of distillation rounds. In each round one or multiple copies of some distillation unit is executed.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/numUnitsPerRound",
+      label: "Distillation units per round",
+      description: `The number of units in each round of distillation`,
+      explanation: `This is the number of copies for the distillation units per round.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/unitNamePerRound",
+      label: "Distillation units",
+      description: `The types of distillation units`,
+      explanation: `These are the types of distillation units that are executed in each round. The units can be either physical or logical, depending on what type of qubit they are operating. Space-efficient units require fewer qubits for the cost of longer runtime compared to Reed-Muller preparation units.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/codeDistancePerRound",
+      label: "Distillation code distances",
+      description: `The code distance in each round of distillation`,
+      explanation: `This is the code distance used for the units in each round. If the code distance is 1, then the distillation unit operates on physical qubits instead of error-corrected logical qubits.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/physicalQubitsPerRound",
+      label: "Number of physical qubits per round",
+      description: `The number of physical qubits used in each round of distillation`,
+      explanation: `The maximum number of physical qubits over all rounds is the number of physical qubits for the T factory, since qubits are reused by different rounds.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/tfactoryRuntimePerRound",
+      label: "Runtime per round",
+      description: `The runtime of each distillation round`,
+      explanation: `The runtime of the T factory is the sum of the runtimes in all rounds.`,
+    });
+    entries.push({
+      path: "physicalCountsFormatted/tstateLogicalErrorRate",
+      label: "Logical T state error rate",
+      description: `Logical T state error rate`,
+      explanation: `This is the logical T state error rate achieved by the T factory which is equal or smaller than the required error rate ${result.physicalCountsFormatted.requiredLogicalTstateErrorRate}.`,
+    });
+    groups.push({
+      title: "T factory parameters",
+      alwaysVisible: false,
+      entries: entries,
+    });
+  }
+  entries = [];
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsNumQubits",
+    label: "Logical qubits (pre-layout)",
+    description: `Number of logical qubits in the input quantum program`,
+    explanation: `We determine ${numberFormat.format(
+      result.physicalCounts.breakdown.algorithmicLogicalQubits,
+    )} algorithmic logical qubits from this number by assuming to align them in a 2D grid. Auxiliary qubits are added to allow for sufficient space to execute multi-qubit Pauli measurements on all or a subset of the logical qubits.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsTCount",
+    label: "T gates",
+    description: `Number of T gates in the input quantum program`,
+    explanation: `This includes all T gates and adjoint T gates, but not T gates used to implement rotation gates with arbitrary angle, CCZ gates, or CCiX gates.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsRotationCount",
+    label: "Rotation gates",
+    description: `Number of rotation gates in the input quantum program`,
+    explanation: `This is the number of all rotation gates. If an angle corresponds to a Pauli, Clifford, or T gate, it is not accounted for in this number.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsRotationDepth",
+    label: "Rotation depth",
+    description: `Depth of rotation gates in the input quantum program`,
+    explanation: `This is the number of all non-Clifford layers that include at least one single-qubit rotation gate with an arbitrary angle.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsCczCount",
+    label: "CCZ gates",
+    description: `Number of CCZ-gates in the input quantum program`,
+    explanation: `This is the number of CCZ gates.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsCcixCount",
+    label: "CCiX gates",
+    description: `Number of CCiX-gates in the input quantum program`,
+    explanation: `This is the number of CCiX gates, which applies $-iX$ controlled on two control qubits [[1212.5069](https://arxiv.org/abs/1212.5069)].`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/logicalCountsMeasurementCount",
+    label: "Measurement operations",
+    description: `Number of single qubit measurements in the input quantum program`,
+    explanation: `This is the number of single qubit measurements in Pauli basis that are used in the input program. Note that all measurements are counted, however, the measurement result is is determined randomly (with a fixed seed) to be 0 or 1 with a probability of 50%.`,
+  });
+  groups.push({
+    title: "Pre-layout logical resources",
+    alwaysVisible: false,
+    entries: entries,
+  });
+
+  entries = [];
+  entries.push({
+    path: "physicalCountsFormatted/errorBudget",
+    label: "Total error budget",
+    description: `Total error budget for the algorithm`,
+    explanation: `The total error budget sets the overall allowed error for the algorithm, i.e., the number of times it is allowed to fail. Its value must be between 0 and 1 and the default value is 0.001, which corresponds to 0.1%, and means that the algorithm is allowed to fail once in 1000 executions. This parameter is highly application specific. For example, if one is running Shor's algorithm for factoring integers, a large value for the error budget may be tolerated as one can check that the output are indeed the prime factors of the input. On the other hand, a much smaller error budget may be needed for an algorithm solving a problem with a solution which cannot be efficiently verified. This budget $\\epsilon = \\epsilon_{\\log} + \\epsilon_{\\rm dis} + \\epsilon_{\\rm syn}$ is uniformly distributed and applies to errors $\\epsilon_{\\log}$ to implement logical qubits, an error budget $\\epsilon_{\\rm dis}$ to produce T states through distillation, and an error budget $\\epsilon_{\\rm syn}$ to synthesize rotation gates with arbitrary angles. Note that for distillation and rotation synthesis, the respective error budgets $\\epsilon_{\\rm dis}$ and $\\epsilon_{\\rm syn}$ are uniformly distributed among all T states and all rotation gates, respectively. If there are no rotation gates in the input algorithm, the error budget is uniformly distributed to logical errors and T state errors.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/errorBudgetLogical",
+    label: "Logical error probability",
+    description: `Probability of at least one logical error`,
+    explanation: `This is one third of the total error budget ${result.physicalCountsFormatted.errorBudget} if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/errorBudgetTstates",
+    label: "T distillation error probability",
+    description: `Probability of at least one faulty T distillation`,
+    explanation: `This is one third of the total error budget ${result.physicalCountsFormatted.errorBudget} if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/errorBudgetRotations",
+    label: "Rotation synthesis error probability",
+    description: `Probability of at least one failed rotation synthesis`,
+    explanation: `This is one third of the total error budget ${result.physicalCountsFormatted.errorBudget}.`,
+  });
+  groups.push({
+    title: "Assumed error budget",
+    alwaysVisible: false,
+    entries: entries,
+  });
+
+  entries = [];
+  entries.push({
+    path: "jobParams/qubitParams/name",
+    label: "Qubit name",
+    description: `Some descriptive name for the qubit model`,
+    explanation: `You can load pre-defined qubit parameters by using the names \`qubit_gate_ns_e3\`, \`qubit_gate_ns_e4\`, \`qubit_gate_us_e3\`, \`qubit_gate_us_e4\`, \`qubit_maj_ns_e4\`, or \`qubit_maj_ns_e6\`. The names of these pre-defined qubit parameters indicate the instruction set (gate-based or Majorana), the operation speed (ns or Âµs regime), as well as the fidelity (e.g., e3 for $10^{-3}$ gate error rates).`,
+  });
+  entries.push({
+    path: "jobParams/qubitParams/instructionSet",
+    label: "Instruction set",
+    description: `Underlying qubit technology (gate-based or Majorana)`,
+    explanation: `When modeling the physical qubit abstractions, we distinguish between two different physical instruction sets that are used to operate the qubits. The physical instruction set can be either _gate-based_ or _Majorana_. A gate-based instruction set provides single-qubit measurement, single-qubit gates (incl. T gates), and two-qubit gates. A Majorana instruction set provides a physical T gate, single-qubit measurement and two-qubit joint measurement operations.`,
+  });
+  entries.push({
+    path: "jobParams/qubitParams/oneQubitMeasurementTime",
+    label: "Single-qubit measurement time",
+    description: `Operation time for single-qubit measurement (t_meas) in ns`,
+    explanation: `This is the operation time in nanoseconds to perform a single-qubit measurement in the Pauli basis.`,
+  });
+  if (result.jobParams.qubitParams.instructionSet == "Majorana") {
+    entries.push({
+      path: "jobParams/qubitParams/twoQubitJointMeasurementTime",
+      label: "Two-qubit measurement time",
+      description: `Operation time for two-qubit measurement in ns`,
+      explanation: `This is the operation time in nanoseconds to perform a non-destructive two-qubit joint Pauli measurement.`,
+    });
+  }
+  if (result.jobParams.qubitParams.instructionSet == "GateBased") {
+    entries.push({
+      path: "jobParams/qubitParams/oneQubitGateTime",
+      label: "Single-qubit gate time",
+      description: `Operation time for single-qubit gate (t_gate) in ns`,
+      explanation: `This is the operation time in nanoseconds to perform a single-qubit Clifford operation, e.g., Hadamard or Phase gates.`,
+    });
+  }
+  if (result.jobParams.qubitParams.instructionSet == "GateBased") {
+    entries.push({
+      path: "jobParams/qubitParams/twoQubitGateTime",
+      label: "Two-qubit gate time",
+      description: `Operation time for two-qubit gate in ns`,
+      explanation: `This is the operation time in nanoseconds to perform a two-qubit Clifford operation, e.g., a CNOT or CZ gate.`,
+    });
+  }
+  entries.push({
+    path: "jobParams/qubitParams/tGateTime",
+    label: "T gate time",
+    description: `Operation time for a T gate`,
+    explanation: `This is the operation time in nanoseconds to execute a T gate.`,
+  });
+  entries.push({
+    path: "jobParams/qubitParams/oneQubitMeasurementErrorRate",
+    label: "Single-qubit measurement error rate",
+    description: `Error rate for single-qubit measurement`,
+    explanation: `This is the probability in which a single-qubit measurement in the Pauli basis may fail.`,
+  });
+  if (result.jobParams.qubitParams.instructionSet == "Majorana") {
+    entries.push({
+      path: "jobParams/qubitParams/twoQubitJointMeasurementErrorRate",
+      label: "Two-qubit measurement error rate",
+      description: `Error rate for two-qubit measurement`,
+      explanation: `This is the probability in which a non-destructive two-qubit joint Pauli measurement may fail.`,
+    });
+  }
+  if (result.jobParams.qubitParams.instructionSet == "GateBased") {
+    entries.push({
+      path: "jobParams/qubitParams/oneQubitGateErrorRate",
+      label: "Single-qubit error rate",
+      description: `Error rate for single-qubit Clifford gate (p)`,
+      explanation: `This is the probability in which a single-qubit Clifford operation, e.g., Hadamard or Phase gates, may fail.`,
+    });
+  }
+  if (result.jobParams.qubitParams.instructionSet == "GateBased") {
+    entries.push({
+      path: "jobParams/qubitParams/twoQubitGateErrorRate",
+      label: "Two-qubit error rate",
+      description: `Error rate for two-qubit Clifford gate`,
+      explanation: `This is the probability in which a two-qubit Clifford operation, e.g., CNOT or CZ gates, may fail.`,
+    });
+  }
+  entries.push({
+    path: "jobParams/qubitParams/tGateErrorRate",
+    label: "T gate error rate",
+    description: `Error rate to prepare single-qubit T state or apply a T gate (p_T)`,
+    explanation: `This is the probability in which executing a single T gate may fail.`,
+  });
+  groups.push({
+    title: "Physical qubit parameters",
+    alwaysVisible: false,
+    entries: entries,
+  });
+
+  entries = [];
+  entries.push({
+    path: "physicalCountsFormatted/logicalDepthFactor",
+    label: "Logical depth factor",
+    description: `Factor, the initial number of logical cycles is multiplied by`,
+    explanation: `This is the factor takes into account a potential overhead to the initial number of logical cycles.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/maxTFactories",
+    label: "Maximum number of T factories",
+    description: `The maximum number of T factories can be utilized during the algorithm's runtime`,
+    explanation: `This is the maximum number of T factories used for producing the demanded T states, which can be created and executed by the algorithm in parallel.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/maxDuration",
+    label: "Maximum runtime duration",
+    description: `The maximum runtime duration allowed for the algorithm runtime`,
+    explanation: `This is the maximum time allowed to the algorithm. If specified, the estimator targets to minimize the number of physical qubits consumed by the algorithm for runtimes under the maximum allowed.`,
+  });
+  entries.push({
+    path: "physicalCountsFormatted/maxPhysicalQubits",
+    label: "Maximum number of physical qubits",
+    description: `The maximum number of physical qubits allowed for utilization to the algorithm`,
+    explanation: `This is the maximum number of physical qubits available to the algorithm. If specified, the estimator targets to minimize the runtime of the algorithm with number of physical qubits consumed not exceeding this maximum.`,
+  });
+  groups.push({ title: "Constraints", alwaysVisible: false, entries: entries });
+
+  const assumptions = [
+    "_More details on the following lists of assumptions can be found in the paper [Accessing requirements for scaling quantum computers and their applications](https://aka.ms/AQ/RE/Paper)._",
+    "**Uniform independent physical noise.** We assume that the noise on physical qubits and physical qubit operations is the standard circuit noise model. In particular we assume error events at different space-time locations are independent and that error rates are uniform across the system in time and space.",
+    "**Efficient classical computation.** We assume that classical overhead (compilation, control, feedback, readout, decoding, etc.) does not dominate the overall cost of implementing the full quantum algorithm.",
+    "**Extraction circuits for planar quantum ISA.** We assume that stabilizer extraction circuits with similar depth and error correction performance to those for standard surface and Hastings-Haah code patches can be constructed to implement all operations of the planar quantum ISA (instruction set architecture).",
+    "**Uniform independent logical noise.** We assume that the error rate of a logical operation is approximately equal to its space-time volume (the number of tiles multiplied by the number of logical time steps) multiplied by the error rate of a logical qubit in a standard one-tile patch in one logical time step.",
+    "**Negligible Clifford costs for synthesis.** We assume that the space overhead for synthesis and space and time overhead for transport of magic states within magic state factories and to synthesis qubits are all negligible.",
+    "**Smooth magic state consumption rate.** We assume that the rate of T state consumption throughout the compiled algorithm is almost constant, or can be made almost constant without significantly increasing the number of logical time steps for the algorithm.",
+  ];
+
+  return { groups: groups, assumptions: assumptions };
+}
+// END OF AUTOMATICALLY GENERATED CODE
diff --git a/npm/ux/resultsTable.tsx b/npm/ux/resultsTable.tsx
index ca7ec0ec..df93d01d 100644
--- a/npm/ux/resultsTable.tsx
+++ b/npm/ux/resultsTable.tsx
@@ -2,14 +2,17 @@
 // Licensed under the MIT License.
 
 import { useRef, useState } from "preact/hooks";
-import { type ReData } from "./reTable.js";
 
-type CellValue = string | number | { value: string; sortBy: number };
+export type CellValue = string | number | { value: string; sortBy: number };
+export type Row = {
+  color: string;
+  cells: CellValue[];
+};
 
 // Note: column 0 is expected to be unique amongst all rows
 export function ResultsTable(props: {
   columnNames: string[];
-  data: ReData[];
+  rows: Row[];
   initialColumns: number[];
   ensureSelected: boolean;
   onRowSelected(rowId: string): void;
@@ -24,20 +27,18 @@ export function ResultsTable(props: {
   const [showColumnMenu, setShowColumnMenu] = useState(false);
   const [showRowMenu, setShowRowMenu] = useState("");
 
-  const rows = props.data.map(ReDataToRow);
-
   // Find the first row that is new in the current sort order
-  const newest = getSortedRows(rows).find(
-    (row) => (row[row.length - 1] as string) === "New",
+  const newest = getSortedRows(props.rows).find(
+    (row) => (row.cells[row.cells.length - 1] as string) === "New",
   );
 
   // Select the first of the newest rows, otherwise preserve the existing selection
   if (newest && props.ensureSelected) {
-    const rowId = newest[0].toString();
+    const rowId = newest.cells[0].toString();
     setSelectedRow(rowId);
     props.onRowSelected(rowId);
-  } else if (!selectedRow && props.ensureSelected && rows.length > 0) {
-    const rowId = rows[0][0].toString();
+  } else if (!selectedRow && props.ensureSelected && props.rows.length > 0) {
+    const rowId = props.rows[0].cells[0].toString();
     setSelectedRow(rowId);
     props.onRowSelected(rowId);
   }
@@ -156,7 +157,7 @@ export function ResultsTable(props: {
     }
   }
 
-  function getSortedRows(rows: CellValue[][]) {
+  function getSortedRows(rows: Row[]) {
     if (!sortColumn) return rows;
 
     const colIdx = sortColumn.columnId;
@@ -164,8 +165,8 @@ export function ResultsTable(props: {
 
     const sortedRows = [...rows];
     sortedRows.sort((a, b) => {
-      const aVal = a[colIdx];
-      const bVal = b[colIdx];
+      const aVal = a.cells[colIdx];
+      const bVal = b.cells[colIdx];
       if (typeof aVal === "string" && typeof bVal === "string") {
         return ascending ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
       } else if (typeof aVal === "number" && typeof bVal === "number") {
@@ -344,8 +345,8 @@ export function ResultsTable(props: {
         </tr>
       </thead>
       <tbody>
-        {getSortedRows(rows).map((row) => {
-          const rowId = row[0].toString();
+        {getSortedRows(props.rows).map((row) => {
+          const rowId = row.cells[0].toString();
           return (
             <tr
               onClick={() => rowClicked(rowId)}
@@ -364,7 +365,7 @@ export function ResultsTable(props: {
                   <svg width="16" height="16" style="position: relative;">
                     <path
                       stroke-width="1.5"
-                      stroke="gray"
+                      stroke={row.color}
                       stroke-linecap="round"
                       d="M4,5 h8 M4,8 h8 M4,11 h8"
                     />
@@ -386,7 +387,9 @@ export function ResultsTable(props: {
               </td>
               {showColumns.map((idx) => {
                 return (
-                  <td data-colid={idx.toString()}>{getCellStr(row[idx])}</td>
+                  <td data-colid={idx.toString()}>
+                    {getCellStr(row.cells[idx])}
+                  </td>
                 );
               })}
             </tr>
@@ -396,25 +399,3 @@ export function ResultsTable(props: {
     </table>
   );
 }
-
-function ReDataToRow(data: ReData): CellValue[] {
-  return [
-    data.jobParams.runName,
-    data.jobParams.qubitParams.name,
-    data.jobParams.qecScheme.name,
-    data.jobParams.errorBudget,
-    data.physicalCounts.breakdown.algorithmicLogicalQubits,
-    data.physicalCounts.breakdown.algorithmicLogicalDepth,
-    data.logicalQubit.codeDistance,
-    data.physicalCounts.breakdown.numTstates,
-    data.physicalCounts.breakdown.numTfactories,
-    data.physicalCountsFormatted.physicalQubitsForTfactoriesPercentage,
-    {
-      value: data.physicalCountsFormatted.runtime,
-      sortBy: data.physicalCounts.runtime,
-    },
-    data.physicalCounts.rqops,
-    data.physicalCounts.physicalQubits,
-    data.new ? "New" : "Cached",
-  ];
-}
diff --git a/npm/ux/scatterChart.tsx b/npm/ux/scatterChart.tsx
new file mode 100644
index 00000000..f7b4636a
--- /dev/null
+++ b/npm/ux/scatterChart.tsx
@@ -0,0 +1,351 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { CreateIntegerTicks, CreateTimeTicks, Tick } from "../src/ux/ticks.js";
+
+export type ScatterSeries = {
+  color: string;
+  items: PlotItem[];
+};
+
+export type PlotItem = {
+  x: number;
+  y: number;
+  label: string;
+};
+
+export type Axis = {
+  isTime: boolean;
+  label: string;
+};
+
+type Range = {
+  min: number;
+  max: number;
+};
+
+export function HideTooltip() {
+  const tooltip = document.getElementById("tooltip");
+  if (tooltip) {
+    tooltip.setAttribute("visibility", "hidden");
+  }
+}
+
+function hideTooltipIfNotClicked() {
+  const tooltip = document.getElementById("tooltip");
+  if (tooltip) {
+    if (tooltip.getAttribute("clicked") === "false") {
+      tooltip.setAttribute("visibility", "hidden");
+    }
+  }
+}
+
+export function ScatterChart(props: {
+  data: ScatterSeries[];
+  xAxis: Axis;
+  yAxis: Axis;
+  onPointSelected(seriesIndex: number, pointIndex: number): void;
+}) {
+  const data = props.data;
+
+  function findMinMaxSingle(
+    series: ScatterSeries,
+  ): [number, number, number, number] {
+    const xs = series.items.map((item) => item.x);
+    const ys = series.items.map((item) => item.y);
+    const minX = Math.min(...xs);
+    const maxX = Math.max(...xs);
+    const minY = Math.min(...ys);
+    const maxY = Math.max(...ys);
+    return [minX, maxX, minY, maxY];
+  }
+
+  function findMinMaxAll(
+    series: ScatterSeries[],
+  ): [number, number, number, number] {
+    const minMax = series.map(findMinMaxSingle);
+    const minX = Math.min(...minMax.map((x) => x[0]));
+    const maxX = Math.max(...minMax.map((x) => x[1]));
+    const minY = Math.min(...minMax.map((x) => x[2]));
+    const maxY = Math.max(...minMax.map((x) => x[3]));
+    return [minX, maxX, minY, maxY];
+  }
+
+  const [minX, maxX, minY, maxY] = findMinMaxAll(data);
+
+  const rangeCoefficient = 2;
+  const rangeX: Range = {
+    min: minX / rangeCoefficient,
+    max: maxX * rangeCoefficient,
+  };
+  const rangeY: Range = {
+    min: minY / rangeCoefficient,
+    max: maxY * rangeCoefficient,
+  };
+
+  function createAxisTicks(range: Range, isTime: boolean): Tick[] {
+    if (isTime) {
+      return CreateTimeTicks(range.min, range.max);
+    } else {
+      return CreateIntegerTicks(range.min, range.max);
+    }
+  }
+
+  const xTicks = createAxisTicks(rangeX, props.xAxis.isTime);
+  const yTicks = createAxisTicks(rangeY, props.yAxis.isTime);
+
+  function coordinateToSvgLogarithmic(
+    value: number,
+    range: Range,
+    size: number,
+  ): number {
+    return (
+      ((Math.log(value) - Math.log(range.min)) /
+        (Math.log(range.max) - Math.log(range.min))) *
+      size
+    );
+  }
+
+  const yAxisTitleWidth = 20;
+  const yAxisTickCaptionMaxWidth = 100;
+  const axisTickLength = 5;
+  const axisLineWidth = 1;
+  const xMargin =
+    yAxisTitleWidth + yAxisTickCaptionMaxWidth + axisTickLength + axisLineWidth;
+
+  const axisTitleHeight = 20;
+  const xAxisTickCaptionMaxHeight = 16;
+  const yMargin =
+    axisTitleHeight +
+    xAxisTickCaptionMaxHeight +
+    axisTickLength +
+    axisLineWidth;
+
+  const svgWidth = 844;
+  const svgViewBoxWidthPadding = 50;
+  const svgHeight = 480;
+  const svgViewBoxHeightPadding = 10;
+  const svgXMin = -xMargin;
+
+  const plotAreaWidth = svgWidth - xMargin;
+  const plotAreaHeight = svgHeight - yMargin;
+
+  const viewBox = `${svgXMin - svgViewBoxWidthPadding} ${
+    -plotAreaHeight - svgViewBoxHeightPadding
+  } ${svgWidth + svgViewBoxWidthPadding} ${
+    svgHeight + svgViewBoxHeightPadding
+  }`;
+
+  const tooltipTextLeftPadding = 5;
+  const tooltipRectanglePaddingHeight = 10;
+  const tooltipTextPaddingHeight = 25;
+
+  const yAxisTextPaddingFromTicks = 5;
+  const yAxisTextYPadding = 6;
+
+  function drawTooltip(
+    text: string,
+    x: number,
+    y: number,
+    seriesIndex: number,
+    pointIndex: number,
+    clicked: boolean = false,
+  ) {
+    const tooltip = document.getElementById("tooltip");
+    const tooltipRect = document.getElementById("tooltipRect");
+    const tooltipText = document.getElementById(
+      "tooltipText",
+    ) as unknown as SVGTextElement;
+
+    if (tooltipText) {
+      tooltipText.setAttribute("x", (x + tooltipTextLeftPadding).toString());
+      tooltipText.setAttribute("y", (y + tooltipTextPaddingHeight).toString());
+      tooltipText.textContent = text;
+    }
+    if (tooltipRect && tooltipText) {
+      const box = tooltipText.getBBox();
+      const textWidth = box.width;
+      tooltipRect.setAttribute(
+        "width",
+        (textWidth + 2 * tooltipTextLeftPadding).toString(),
+      );
+      tooltipRect.setAttribute("x", x.toString());
+      tooltipRect.setAttribute(
+        "y",
+        (y + tooltipRectanglePaddingHeight).toString(),
+      );
+    }
+    if (tooltip) {
+      tooltip.setAttribute("visibility", "visible");
+      tooltip.setAttribute("clicked", clicked.toString());
+    }
+    props.onPointSelected(seriesIndex, pointIndex);
+  }
+
+  return (
+    <div style="display: flex; flex-wrap: wrap; margin-top: 8px;">
+      <div class="chart-container">
+        <svg
+          width={svgWidth}
+          height={svgHeight}
+          viewBox={viewBox}
+          id="scatterChart"
+        >
+          <line
+            id="xAxis"
+            x1="0"
+            y1="0"
+            x2={plotAreaWidth}
+            y2="0"
+            stroke="var(--border-color)"
+          />
+
+          {xTicks.map((tick) => {
+            const x = coordinateToSvgLogarithmic(
+              tick.value,
+              rangeX,
+              plotAreaWidth,
+            );
+            return (
+              <g>
+                <line
+                  x1={x}
+                  y1="1"
+                  x2={x}
+                  y2={axisTickLength}
+                  stroke="var(--border-color)"
+                />
+                <text
+                  x={x}
+                  y={axisTickLength + xAxisTickCaptionMaxHeight}
+                  text-anchor="middle"
+                  fill="var(--main-color)"
+                >
+                  {tick.label}
+                </text>
+              </g>
+            );
+          })}
+
+          <line
+            id="yAxis"
+            x1="0"
+            y1="0"
+            x2="0"
+            y2={-svgHeight}
+            stroke="var(--border-color)"
+          />
+
+          {yTicks.map((tick) => {
+            const y = -coordinateToSvgLogarithmic(
+              tick.value,
+              rangeY,
+              plotAreaHeight,
+            );
+            return (
+              <g>
+                <line
+                  x1="0"
+                  y1={y}
+                  x2={-axisTickLength}
+                  y2={y}
+                  stroke="var(--border-color)"
+                />
+                <text
+                  x={-axisTickLength - yAxisTextPaddingFromTicks}
+                  y={y + yAxisTextYPadding}
+                  text-anchor="end"
+                  fill="var(--main-color)"
+                >
+                  {tick.label}
+                </text>
+              </g>
+            );
+          })}
+
+          <text
+            x={plotAreaWidth / 2}
+            y={yMargin}
+            class="qs-scatterChart-x-axisTitle"
+          >
+            {props.xAxis.label} (logarithmic)
+          </text>
+
+          <text
+            x={xMargin - axisTitleHeight}
+            y={plotAreaHeight / 2}
+            class="qs-scatterChart-y-axisTitle"
+          >
+            {props.yAxis.label} (logarithmic)
+          </text>
+
+          <text
+            class="qs-scatterChart-watermark"
+            x={xMargin - axisTitleHeight}
+            y={-svgHeight + yMargin}
+          >
+            Created with Azure Quantum Resource Estimator
+          </text>
+
+          {data.map((data, seriesIndex) => {
+            return data.items.map((item, pointIndex) => {
+              const x = coordinateToSvgLogarithmic(
+                item.x,
+                rangeX,
+                plotAreaWidth,
+              );
+
+              const y = -coordinateToSvgLogarithmic(
+                item.y,
+                rangeY,
+                plotAreaHeight,
+              );
+              return (
+                <circle
+                  id={`point-${seriesIndex}-${pointIndex}`}
+                  cx={x}
+                  cy={y}
+                  class="qs-scatterChart-point"
+                  stroke={data.color}
+                  onMouseOver={() =>
+                    drawTooltip(
+                      item.label,
+                      x,
+                      y,
+                      seriesIndex,
+                      pointIndex,
+                      false,
+                    )
+                  }
+                  onClick={() =>
+                    drawTooltip(item.label, x, y, seriesIndex, pointIndex, true)
+                  }
+                  onMouseOut={() => hideTooltipIfNotClicked()}
+                />
+              );
+            });
+          })}
+          <g id="tooltip" visibility="hidden">
+            <rect
+              id="tooltipRect"
+              x="100"
+              y="-100"
+              width="200"
+              height="22"
+              fill="white"
+              stroke="black"
+              stroke-width="1"
+            />
+            <text
+              id="tooltipText"
+              x="105"
+              y="115"
+              text-anchor="left"
+              fill="black"
+            ></text>
+          </g>
+        </svg>
+      </div>
+    </div>
+  );
+}
diff --git a/npm/ux/spaceChart.tsx b/npm/ux/spaceChart.tsx
index dc954743..4441a5f5 100644
--- a/npm/ux/spaceChart.tsx
+++ b/npm/ux/spaceChart.tsx
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import { ReData } from "./reTable.js";
+import { SingleEstimateResult } from "./data.js";
 
 function getPieSegment(
   x: number,
@@ -26,7 +26,7 @@ function getPieSegment(
   return d;
 }
 
-export function SpaceChart(props: { estimatesData: ReData }) {
+export function SpaceChart(props: { estimatesData: SingleEstimateResult }) {
   const breakdown = props.estimatesData.physicalCounts.breakdown;
 
   // The values to be shown on the pie chart
diff --git a/package-lock.json b/package-lock.json
index 38757671..34d63d74 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,7 @@
         "punycode": "^2.3.1",
         "typescript": "^5.2.2",
         "url": "^0.11.3",
+        "user-agent-data-types": "^0.4.2",
         "vscode-uri": "^3.0.8"
       },
       "engines": {
@@ -4632,6 +4633,12 @@
       "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
       "dev": true
     },
+    "node_modules/user-agent-data-types": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/user-agent-data-types/-/user-agent-data-types-0.4.2.tgz",
+      "integrity": "sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw==",
+      "dev": true
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index 1a546d88..0c1eb99a 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "punycode": "^2.3.1",
     "typescript": "^5.2.2",
     "url": "^0.11.3",
+    "user-agent-data-types": "^0.4.2",
     "vscode-uri": "^3.0.8"
   }
 }
diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi
index 92e8b4b5..d96b72e3 100644
--- a/pip/qsharp/_native.pyi
+++ b/pip/qsharp/_native.pyi
@@ -57,17 +57,15 @@ class Interpreter:
         """
         ...
     def run(
-        self, entry_expr: str, shots: int, output_fn: Callable[[Output], None]
+        self, entry_expr: str, output_fn: Callable[[Output], None]
     ) -> Any:
         """
-        Runs the given Q# expressin for the given number of shots.
-        Each shot uses an independent instance of the simulator.
+        Runs the given Q# expression with an independent instance of the simulator.
 
         :param entry_expr: The entry expression.
-        :param shots: The number of shots to run.
         :param output_fn: A callback function that will be called with each output.
 
-        :returns values: A list of results or runtime errors.
+        :returns values: A result or runtime errors.
 
         :raises QSharpError: If there is an error interpreting the input.
         """
diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py
index 4573ffd0..9b279ca5 100644
--- a/pip/qsharp/_qsharp.py
+++ b/pip/qsharp/_qsharp.py
@@ -163,9 +163,9 @@ def run(
     for _ in range(shots):
         results.append({"result": None, "events": []})
         run_results = get_interpreter().run(
-            entry_expr, 1, on_save_events if save_events else print_output
+            entry_expr, on_save_events if save_events else print_output
         )
-        results[-1]["result"] = run_results[0]
+        results[-1]["result"] = run_results
         if on_result:
             on_result(results[-1])
 
diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs
index 1f3c8fde..4ac91b3e 100644
--- a/pip/src/interpreter.rs
+++ b/pip/src/interpreter.rs
@@ -19,8 +19,9 @@ use pyo3::{
 use qsc::{
     fir,
     interpret::{
+        self,
         output::{Error, Receiver},
-        stateful, Value,
+        Value,
     },
     project::{FileSystem, Manifest, ManifestDescriptor},
     target::Profile,
@@ -63,7 +64,7 @@ pub(crate) enum TargetProfile {
 
 #[pyclass(unsendable)]
 pub(crate) struct Interpreter {
-    pub(crate) interpreter: stateful::Interpreter,
+    pub(crate) interpreter: interpret::Interpreter,
 }
 
 pub(crate) struct PyManifestDescriptor(ManifestDescriptor);
@@ -125,7 +126,7 @@ impl Interpreter {
             SourceMap::default()
         };
 
-        match stateful::Interpreter::new(true, sources, PackageType::Lib, target.into()) {
+        match interpret::Interpreter::new(true, sources, PackageType::Lib, target.into()) {
             Ok(interpreter) => Ok(Self { interpreter }),
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
@@ -167,19 +168,14 @@ impl Interpreter {
         &mut self,
         py: Python,
         entry_expr: &str,
-        shots: u32,
         callback: Option<PyObject>,
-    ) -> PyResult<Py<PyList>> {
+    ) -> PyResult<PyObject> {
         let mut receiver = OptionalCallbackReceiver { callback, py };
-        match self.interpreter.run(&mut receiver, entry_expr, shots) {
-            Ok(results) => Ok(PyList::new(
-                py,
-                results.into_iter().map(|res| match res {
-                    Ok(v) => ValueWrapper(v).into_py(py),
-                    Err(errors) => QSharpError::new_err(format_errors(errors)).into_py(py),
-                }),
-            )
-            .into_py(py)),
+        match self.interpreter.run(&mut receiver, entry_expr) {
+            Ok(result) => match result {
+                Ok(v) => Ok(ValueWrapper(v).into_py(py)),
+                Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
+            },
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
     }
@@ -234,7 +230,7 @@ create_exception!(
     "An error returned from the Q# interpreter."
 );
 
-fn format_errors(errors: Vec<stateful::Error>) -> String {
+fn format_errors(errors: Vec<interpret::Error>) -> String {
     errors
         .into_iter()
         .map(|e| {
@@ -254,8 +250,8 @@ fn format_errors(errors: Vec<stateful::Error>) -> String {
 }
 
 /// Additional help text for an error specific to the Python module
-fn python_help(error: &stateful::Error) -> Option<String> {
-    if matches!(error, stateful::Error::UnsupportedRuntimeCapabilities) {
+fn python_help(error: &interpret::Error) -> Option<String> {
+    if matches!(error, interpret::Error::UnsupportedRuntimeCapabilities) {
         Some("Unsupported target profile. Initialize Q# by running `qsharp.init(target_profile=qsharp.TargetProfile.Base)` before performing code generation.".into())
     } else {
         None
@@ -504,7 +500,7 @@ impl IntoPyErr for Report {
     }
 }
 
-impl IntoPyErr for Vec<stateful::Error> {
+impl IntoPyErr for Vec<interpret::Error> {
     fn into_py_err(self) -> PyErr {
         let mut message = String::new();
         for error in self {
diff --git a/resource_estimator/src/counts.rs b/resource_estimator/src/counts.rs
index c956b1a3..0557b7a3 100644
--- a/resource_estimator/src/counts.rs
+++ b/resource_estimator/src/counts.rs
@@ -522,10 +522,6 @@ impl Backend for LogicalCounter {
         true
     }
 
-    fn reinit(&mut self) {
-        *self = Self::default();
-    }
-
     fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
         match name {
             "BeginEstimateCaching" => {
diff --git a/resource_estimator/src/estimates/modeling/logical_qubit.rs b/resource_estimator/src/estimates/modeling/logical_qubit.rs
index f1f4df77..3935398f 100644
--- a/resource_estimator/src/estimates/modeling/logical_qubit.rs
+++ b/resource_estimator/src/estimates/modeling/logical_qubit.rs
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #[cfg(test)]
 mod tests;
 
diff --git a/resource_estimator/src/lib.rs b/resource_estimator/src/lib.rs
index d82b8cf0..d294011f 100644
--- a/resource_estimator/src/lib.rs
+++ b/resource_estimator/src/lib.rs
@@ -9,10 +9,7 @@ pub use estimates::estimate_physical_resources_from_json;
 use counts::LogicalCounter;
 use estimates::estimate_physical_resources;
 use miette::Diagnostic;
-use qsc::interpret::{
-    stateful::{self, Interpreter},
-    GenericReceiver,
-};
+use qsc::interpret::{self, GenericReceiver, Interpreter};
 use thiserror::Error;
 
 #[derive(Debug)]
@@ -29,7 +26,7 @@ pub struct LogicalResources {
 #[error(transparent)]
 #[diagnostic(transparent)]
 pub enum Error {
-    Interpreter(stateful::Error),
+    Interpreter(interpret::Error),
     Estimation(estimates::Error),
 }
 
@@ -53,11 +50,8 @@ pub fn estimate_expr(
     let mut stdout = std::io::sink();
     let mut out = GenericReceiver::new(&mut stdout);
     interpreter
-        .run_with_sim(&mut counter, &mut out, expr, 1)
+        .run_with_sim(&mut counter, &mut out, expr)
         .map_err(|e| e.into_iter().map(Error::Interpreter).collect::<Vec<_>>())?
-        .into_iter()
-        .last()
-        .expect("execution should have at least one result")
         .map_err(|e| vec![Error::Interpreter(e[0].clone())])?;
     estimate_physical_resources(&counter.logical_resources(), params)
         .map_err(|e| vec![Error::Estimation(e)])
diff --git a/samples/estimation/estimation-frontier-widgets.ipynb b/samples/estimation/estimation-frontier-widgets.ipynb
new file mode 100644
index 00000000..f2eafe41
--- /dev/null
+++ b/samples/estimation/estimation-frontier-widgets.ipynb
@@ -0,0 +1,83 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import qsharp\n",
+    "from qsharp_widgets import SpaceChart, EstimateDetails, EstimatesOverview\n",
+    "with open(\"Dynamics.qs\", \"r\") as f:\n",
+    "    contents = f.read()\n",
+    "qsharp.eval(contents)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "result = qsharp.estimate(\n",
+    "    \"QuantumDynamics.Main()\",\n",
+    "    [\n",
+    "        {\n",
+    "        \"qubitParams\": { \"name\": \"qubit_maj_ns_e4\" },\n",
+    "        \"qecScheme\": { \"name\": \"surface_code\" },\n",
+    "        \"estimateType\": \"frontier\",\n",
+    "        },\n",
+    "        {\n",
+    "        \"qubitParams\": { \"name\": \"qubit_maj_ns_e6\" },\n",
+    "        \"qecScheme\": { \"name\": \"floquet_code\" },\n",
+    "        \"estimateType\": \"frontier\",\n",
+    "        },\n",
+    "    ]\n",
+    ")\n",
+    "\n",
+    "EstimatesOverview(result, colors=[\"#1f77b4\", \"#ff7f0e\"], runNames=[\"e4 Surface Code\", \"e6 Floquet Code\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Qubit usage diagram for the first (estimate index=0) run and the third (point index=2) shortest runtime\n",
+    "SpaceChart(result[0], 2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Estimate details usage for the second (estimate index=0) run and the fourth (point index=3) shortest runtime\n",
+    "EstimateDetails(result[1], 4)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.12.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/vscode/src/azure/networkRequests.ts b/vscode/src/azure/networkRequests.ts
index d5e419fb..5977e316 100644
--- a/vscode/src/azure/networkRequests.ts
+++ b/vscode/src/azure/networkRequests.ts
@@ -2,7 +2,12 @@
 // Licensed under the MIT License.
 
 import { log } from "qsharp-lang";
-import { EventType, UserFlowStatus, sendTelemetryEvent } from "../telemetry";
+import {
+  EventType,
+  UserFlowStatus,
+  getUserAgent,
+  sendTelemetryEvent,
+} from "../telemetry";
 import { getRandomGuid } from "../utils";
 
 const publicMgmtEndpoint = "https://management.azure.com";
@@ -19,6 +24,7 @@ export async function azureRequest(
   const headers: [string, string][] = [
     ["Authorization", `Bearer ${token}`],
     ["Content-Type", "application/json"],
+    ["User-Agent", getUserAgent()],
   ];
 
   try {
@@ -75,6 +81,7 @@ export async function storageRequest(
   const headers: [string, string][] = [
     ["x-ms-version", "2023-01-03"],
     ["x-ms-date", new Date().toUTCString()],
+    ["User-Agent", getUserAgent()],
   ];
   if (token) headers.push(["Authorization", `Bearer ${token}`]);
 
diff --git a/vscode/src/notebookTemplate.ts b/vscode/src/notebookTemplate.ts
index a948af79..78cc8f5d 100644
--- a/vscode/src/notebookTemplate.ts
+++ b/vscode/src/notebookTemplate.ts
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 import * as vscode from "vscode";
 
 // The VS Code notebook representation does not exactly match the ipynb JSON structure,
diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts
index 6b522654..4f41bf82 100644
--- a/vscode/src/telemetry.ts
+++ b/vscode/src/telemetry.ts
@@ -1,3 +1,8 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/// <reference types="user-agent-data-types" />
+
 import * as vscode from "vscode";
 import TelemetryReporter from "@vscode/extension-telemetry";
 import { log } from "qsharp-lang";
@@ -223,6 +228,7 @@ export enum DebugEvent {
 }
 
 let reporter: TelemetryReporter | undefined;
+let userAgentString: string | undefined;
 
 export function initTelemetry(context: vscode.ExtensionContext) {
   const packageJson = context.extension?.packageJSON;
@@ -230,6 +236,9 @@ export function initTelemetry(context: vscode.ExtensionContext) {
     return;
   }
   reporter = new TelemetryReporter(packageJson.aiKey);
+  const version = context.extension?.packageJSON?.version;
+  const browserAndRelease = getBrowserRelease();
+  userAgentString = `VSCode/${version} ${browserAndRelease}`;
 
   sendTelemetryEvent(EventType.InitializePlugin, {}, {});
 }
@@ -250,3 +259,17 @@ export function sendTelemetryEvent<E extends keyof EventTypes>(
     )}`,
   );
 }
+
+function getBrowserRelease(): string {
+  if (navigator.userAgentData?.brands) {
+    const browser =
+      navigator.userAgentData.brands[navigator.userAgentData.brands.length - 1];
+    return `${browser.brand}/${browser.version}`;
+  } else {
+    return "";
+  }
+}
+
+export function getUserAgent(): string {
+  return userAgentString || navigator.userAgent;
+}
diff --git a/vscode/src/webview/rePage.tsx b/vscode/src/webview/rePage.tsx
index c5b9c508..eeb65878 100644
--- a/vscode/src/webview/rePage.tsx
+++ b/vscode/src/webview/rePage.tsx
@@ -2,25 +2,13 @@
 // Licensed under the MIT License.
 
 import { useState } from "preact/hooks";
-import { ResultsTable, SpaceChart, ReTable, type ReData } from "qsharp-lang/ux";
-
-const columnNames = [
-  "Run name",
-  "Qubit type",
-  "QEC scheme",
-  "Error budget",
-  "Logical qubits",
-  "Logical depth",
-  "Code distance",
-  "T states",
-  "T factories",
-  "T factory fraction",
-  "Runtime",
-  "rQOPS",
-  "Physical qubits",
-];
-
-const initialColumns = [0, 1, 2, 3, 4, 10, 11, 12];
+import {
+  SpaceChart,
+  ReTable,
+  type ReData,
+  EstimatesOverview,
+} from "qsharp-lang/ux";
+import { SingleEstimateResult } from "qsharp-lang/ux/data";
 
 export function RePage(props: {
   estimatesData: ReData[];
@@ -28,25 +16,7 @@ export function RePage(props: {
   renderer: (input: string) => string;
   onRowDeleted: (rowId: string) => void;
 }) {
-  const [estimate, setEstimate] = useState<ReData | null>(null);
-
-  function onRowSelected(rowId: string) {
-    // On any selection, clear the "new" flag on all rows. This ensures that
-    // new rows do not steal focus from the user selected row.
-    props.estimatesData.forEach((data) => (data.new = false));
-    if (!rowId) {
-      setEstimate(null);
-    } else {
-      setEstimate(
-        props.estimatesData.find((data) => data.jobParams.runName === rowId) ||
-          null,
-      );
-    }
-  }
-
-  function onRowDeleted(rowId: string) {
-    props.onRowDeleted(rowId);
-  }
+  const [estimate, setEstimate] = useState<SingleEstimateResult | null>(null);
 
   return (
     <>
@@ -96,19 +66,14 @@ export function RePage(props: {
         ) : null}
         <h1>Azure Quantum Resource Estimator</h1>
       </div>
-      <details open>
-        <summary style="font-size: 1.5em; font-weight: bold; margin: 24px 8px;">
-          Results
-        </summary>
-        <ResultsTable
-          columnNames={columnNames}
-          data={props.estimatesData}
-          initialColumns={initialColumns}
-          ensureSelected={true}
-          onRowSelected={onRowSelected}
-          onRowDeleted={onRowDeleted}
-        />
-      </details>
+      <EstimatesOverview
+        estimatesData={props.estimatesData}
+        isSimplifiedView={false}
+        onRowDeleted={props.onRowDeleted}
+        setEstimate={setEstimate}
+        runNames={null}
+        colors={null}
+      ></EstimatesOverview>
       {!estimate ? null : (
         <>
           <details open>
diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts
index f07427bf..3e46f205 100644
--- a/vscode/src/webviewPanel.ts
+++ b/vscode/src/webviewPanel.ts
@@ -167,6 +167,7 @@ export function registerWebViewCommands(context: ExtensionContext) {
       const params = qubitType.map((item) => ({
         ...item.params,
         errorBudget: parseFloat(errorBudget),
+        estimateType: "frontier",
       }));
 
       log.info("RE params", params);
diff --git a/wasm/src/debug_service.rs b/wasm/src/debug_service.rs
index fdf7dfbf..eac5db6a 100644
--- a/wasm/src/debug_service.rs
+++ b/wasm/src/debug_service.rs
@@ -4,8 +4,7 @@
 use std::str::FromStr;
 
 use qsc::fir::StmtId;
-use qsc::interpret::stateful::Interpreter;
-use qsc::interpret::{stateful, StepAction, StepResult};
+use qsc::interpret::{Error, Interpreter, StepAction, StepResult};
 use qsc::{fmt_complex, target::Profile, PackageType, SourceMap};
 
 use crate::{get_source_map, serializable_type, CallbackReceiver};
@@ -146,7 +145,7 @@ impl DebugService {
         event_cb: F,
         bps: &[StmtId],
         step: StepAction,
-    ) -> Result<StepResult, Vec<stateful::Error>>
+    ) -> Result<StepResult, Vec<Error>>
     where
         F: Fn(&str),
     {
@@ -219,7 +218,7 @@ impl Default for DebugService {
     }
 }
 
-fn render_errors(errors: Vec<qsc::interpret::stateful::Error>) -> String {
+fn render_errors(errors: Vec<Error>) -> String {
     let mut msg = String::new();
     for error in errors {
         let error_string = render_error(error);
@@ -228,7 +227,7 @@ fn render_errors(errors: Vec<qsc::interpret::stateful::Error>) -> String {
     msg
 }
 
-fn render_error(error: qsc::interpret::stateful::Error) -> String {
+fn render_error(error: Error) -> String {
     format!("{:?}\n", miette::Report::new(error))
 }
 
diff --git a/wasm/src/diagnostic.rs b/wasm/src/diagnostic.rs
index eb76f423..eda2086d 100644
--- a/wasm/src/diagnostic.rs
+++ b/wasm/src/diagnostic.rs
@@ -3,7 +3,7 @@
 
 use crate::serializable_type;
 use miette::{Diagnostic, LabeledSpan, Severity};
-use qsc::{self, error::WithSource, interpret::stateful, SourceName};
+use qsc::{self, error::WithSource, interpret, SourceName};
 use serde::{Deserialize, Serialize};
 use std::{fmt::Write, iter};
 use wasm_bindgen::prelude::*;
@@ -52,16 +52,13 @@ impl VSDiagnostic {
     }
 
     /// Creates a [VSDiagnostic] from an interpreter error. See `VSDiagnostic::new()` for details.
-    pub(crate) fn from_interpret_error(
-        source_name: &str,
-        err: &qsc::interpret::stateful::Error,
-    ) -> Self {
+    pub(crate) fn from_interpret_error(source_name: &str, err: &interpret::Error) -> Self {
         let labels = match err {
-            stateful::Error::Compile(e) => error_labels(e),
-            stateful::Error::Pass(e) => error_labels(e),
-            stateful::Error::Eval(e) => error_labels(e.error()),
-            stateful::Error::NoEntryPoint => Vec::new(),
-            stateful::Error::UnsupportedRuntimeCapabilities => Vec::new(),
+            interpret::Error::Compile(e) => error_labels(e),
+            interpret::Error::Pass(e) => error_labels(e),
+            interpret::Error::Eval(e) => error_labels(e.error()),
+            interpret::Error::NoEntryPoint => Vec::new(),
+            interpret::Error::UnsupportedRuntimeCapabilities => Vec::new(),
         };
 
         Self::new(labels, source_name, err)
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index f2643acd..7d3f58bd 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -12,8 +12,8 @@ use qsc::{
     compile,
     hir::PackageId,
     interpret::{
+        self,
         output::{self, Receiver},
-        stateful,
     },
     target::Profile,
     PackageStore, PackageType, SourceContents, SourceMap, SourceName, SparseSim,
@@ -102,7 +102,7 @@ fn _get_qir(sources: SourceMap) -> Result<String, String> {
 pub fn get_estimates(sources: Vec<js_sys::Array>, params: &str) -> Result<String, String> {
     let sources = get_source_map(sources, None);
 
-    let mut interpreter = stateful::Interpreter::new(
+    let mut interpreter = interpret::Interpreter::new(
         true,
         sources,
         PackageType::Exe,
@@ -111,7 +111,7 @@ pub fn get_estimates(sources: Vec<js_sys::Array>, params: &str) -> Result<String
     .map_err(|e| e[0].to_string())?;
 
     estimate_entry(&mut interpreter, params).map_err(|e| match &e[0] {
-        re::Error::Interpreter(stateful::Error::Eval(e)) => e.to_string(),
+        re::Error::Interpreter(interpret::Error::Eval(e)) => e.to_string(),
         re::Error::Interpreter(_) => unreachable!("interpreter errors should be eval errors"),
         re::Error::Estimation(e) => e.to_string(),
     })
@@ -202,7 +202,7 @@ where
     }
 }
 
-fn run_internal<F>(sources: SourceMap, event_cb: F, shots: u32) -> Result<(), Box<stateful::Error>>
+fn run_internal<F>(sources: SourceMap, event_cb: F, shots: u32) -> Result<(), Box<interpret::Error>>
 where
     F: FnMut(&str),
 {
@@ -213,7 +213,7 @@ where
         .expect("There must be a source to process")
         .to_string();
     let mut out = CallbackReceiver { event_cb };
-    let mut interpreter = match stateful::Interpreter::new(
+    let mut interpreter = match interpret::Interpreter::new(
         true,
         sources,
         PackageType::Exe,
diff --git a/wasm/src/project_system.rs b/wasm/src/project_system.rs
index 0a5a1b5c..f203c4ec 100644
--- a/wasm/src/project_system.rs
+++ b/wasm/src/project_system.rs
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 use async_trait::async_trait;
 use js_sys::JsString;
 use qsc_project::{EntryType, JSFileEntry, Manifest, ManifestDescriptor, ProjectSystemCallbacks};
diff --git a/widgets/js/index.tsx b/widgets/js/index.tsx
index b979daf9..a7c3d90b 100644
--- a/widgets/js/index.tsx
+++ b/widgets/js/index.tsx
@@ -2,7 +2,13 @@
 // Licensed under the MIT License.
 
 import { render as prender } from "preact";
-import { ReTable, SpaceChart, Histogram } from "qsharp-lang/ux";
+import {
+  ReTable,
+  SpaceChart,
+  Histogram,
+  CreateSingleEstimateResult,
+  EstimatesOverview,
+} from "qsharp-lang/ux";
 import markdownIt from "markdown-it";
 
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -20,6 +26,7 @@ export function mdRenderer(input: string) {
 
 // Param types for AnyWidget render functions
 import type { AnyModel } from "@anywidget/types";
+
 type RenderArgs = {
   model: AnyModel;
   el: HTMLElement;
@@ -32,6 +39,9 @@ export function render({ model, el }: RenderArgs) {
     case "SpaceChart":
       renderChart({ model, el });
       break;
+    case "EstimatesOverview":
+      renderEstimatesOverview({ model, el });
+      break;
     case "EstimateDetails":
       renderTable({ model, el });
       break;
@@ -46,24 +56,66 @@ export function render({ model, el }: RenderArgs) {
 function renderTable({ model, el }: RenderArgs) {
   const onChange = () => {
     const estimates = model.get("estimates");
+    const index = model.get("index");
+    const singleEstimateResult = CreateSingleEstimateResult(estimates, index);
     prender(
-      <ReTable estimatesData={estimates} mdRenderer={mdRenderer}></ReTable>,
+      <ReTable
+        estimatesData={singleEstimateResult}
+        mdRenderer={mdRenderer}
+      ></ReTable>,
       el,
     );
   };
 
   onChange();
   model.on("change:estimates", onChange);
+  model.on("change:index", onChange);
 }
 
 function renderChart({ model, el }: RenderArgs) {
   const onChange = () => {
     const estimates = model.get("estimates");
-    prender(<SpaceChart estimatesData={estimates}></SpaceChart>, el);
+    const index = model.get("index");
+    const singleEstimateResult = CreateSingleEstimateResult(estimates, index);
+    prender(<SpaceChart estimatesData={singleEstimateResult}></SpaceChart>, el);
+  };
+
+  onChange();
+  model.on("change:estimates", onChange);
+  model.on("change:index", onChange);
+}
+
+function renderEstimatesOverview({ model, el }: RenderArgs) {
+  const onChange = () => {
+    const results = model.get("estimates");
+    const colors = model.get("colors");
+    const runNames = model.get("runNames");
+
+    const estimates = [];
+    if (results[0] == null) {
+      estimates.push(results);
+    } else {
+      for (const estimate of Object.values(results)) {
+        estimates.push(estimate);
+      }
+    }
+
+    prender(
+      <EstimatesOverview
+        estimatesData={estimates}
+        runNames={runNames}
+        colors={colors}
+        isSimplifiedView={true}
+        onRowDeleted={() => undefined}
+        setEstimate={() => undefined}
+      ></EstimatesOverview>,
+      el,
+    );
   };
 
   onChange();
   model.on("change:estimates", onChange);
+  model.on("change:colors", onChange);
 }
 
 function renderHistogram({ model, el }: RenderArgs) {
diff --git a/widgets/src/qsharp_widgets/__init__.py b/widgets/src/qsharp_widgets/__init__.py
index 5bb38c10..09fceb23 100644
--- a/widgets/src/qsharp_widgets/__init__.py
+++ b/widgets/src/qsharp_widgets/__init__.py
@@ -20,10 +20,46 @@ class SpaceChart(anywidget.AnyWidget):
 
     comp = traitlets.Unicode("SpaceChart").tag(sync=True)
     estimates = traitlets.Dict().tag(sync=True)
+    index = traitlets.Integer().tag(sync=True)
 
-    def __init__(self, estimates):
+    def __init__(self, estimates, index=None):
+        """
+        This function generates a chart for the qubit utilization of the estimates.
+
+        Parameters:
+        - estimates: data for the chart.
+        - index (optional): the index of the estimate to be displayed. In case of a single point estimate, the parameter is ignored. In case of the frontier estimate, indexes correspond to points on frontier from the shortest runtime to the longest one. If not provided, the shortest runtime estimate is displayed.
+        """
         super().__init__()
         self.estimates = estimates
+        self.index = 0 if index is None else index
+
+
+class EstimatesOverview(anywidget.AnyWidget):
+    _esm = pathlib.Path(__file__).parent / "static" / "index.js"
+    _css = pathlib.Path(__file__).parent / "static" / "index.css"
+
+    comp = traitlets.Unicode("EstimatesOverview").tag(sync=True)
+    estimates = traitlets.Dict().tag(sync=True)
+    colors = traitlets.List().tag(sync=True)
+    runNames = traitlets.List().tag(sync=True)
+
+    def __init__(self, estimates, colors=None, runNames=None):
+        """
+        This function generates a summary results table with a qubit-time diagram.
+
+        Parameters:
+        - estimates: data for the table and the diagram.
+        - colors (optional): the list of colors which could be provided in the hex form or by name. If the length of the list does not match the number of the estimates, the colors parameter will be ignored and replaced with defaults.
+        - runNames (optional): the list of the run names. If the length of the list does not match the number of the estimates, the runNames parameter will be ignored and replaced with defaults.
+
+        Returns:
+        None
+        """
+        super().__init__()
+        self.estimates = estimates
+        self.colors = [] if colors is None else colors
+        self.runNames = [] if runNames is None else runNames
 
 
 class EstimateDetails(anywidget.AnyWidget):
@@ -32,10 +68,19 @@ class EstimateDetails(anywidget.AnyWidget):
 
     comp = traitlets.Unicode("EstimateDetails").tag(sync=True)
     estimates = traitlets.Dict().tag(sync=True)
+    index = traitlets.Integer().tag(sync=True)
+
+    def __init__(self, estimates, index=None):
+        """
+        This function generates a report for the qubit utilization of the estimates.
 
-    def __init__(self, estimates):
+        Parameters:
+        - estimates: data for the report.
+        - index (optional): the index of the estimate to be displayed. In case of a single point estimate, the parameter is ignored. In case of the frontier estimate, indexes correspond to points on frontier from the shortest runtime to the longest one. If not provided, the shortest runtime estimate is displayed.
+        """
         super().__init__()
         self.estimates = estimates
+        self.index = 0 if index is None else index
 
 
 class Histogram(anywidget.AnyWidget):


Test patch:
diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs
index ef68f0a0..c0bba739 100644
--- a/compiler/qsc/src/interpret/debug/tests.rs
+++ b/compiler/qsc/src/interpret/debug/tests.rs
@@ -10,7 +10,7 @@ use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
 use qsc_passes::PackageType;
 use std::io::Cursor;
 
-use crate::interpret::stateful::{self, InterpretResult, Interpreter};
+use crate::interpret::{Error, InterpretResult, Interpreter};
 
 fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (InterpretResult, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
@@ -21,7 +21,7 @@ fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (InterpretResul
     )
 }
 
-fn eval(interpreter: &mut stateful::Interpreter) -> (Result<Value, Vec<stateful::Error>>, String) {
+fn eval(interpreter: &mut Interpreter) -> (Result<Value, Vec<Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     (interpreter.eval_entry(&mut receiver), receiver.dump())
diff --git a/compiler/qsc/src/interpret/stateful/stepping_tests.rs b/compiler/qsc/src/interpret/stepping_tests.rs
similarity index 93%
rename from compiler/qsc/src/interpret/stateful/stepping_tests.rs
rename to compiler/qsc/src/interpret/stepping_tests.rs
index 958f3e3b..415e4983 100644
--- a/compiler/qsc/src/interpret/stateful/stepping_tests.rs
+++ b/compiler/qsc/src/interpret/stepping_tests.rs
@@ -3,7 +3,7 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::interpret::stateful::Interpreter;
+use crate::interpret::Interpreter;
 use qsc_eval::{output::CursorReceiver, StepAction, StepResult};
 use qsc_fir::fir::StmtId;
 use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
@@ -38,30 +38,21 @@ fn expect_bp(interpreter: &mut Interpreter, ids: &[StmtId], expected_id: StmtId)
 fn step_in(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::In)
 }
 
 fn step_next(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::Next)
 }
 
 fn step_out(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::Out)
 }
 
@@ -69,10 +60,7 @@ fn step(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
     step: StepAction,
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     (
@@ -137,7 +125,7 @@ mod given_interpreter_with_sources {
         use super::*;
 
         #[test]
-        fn in_one_level_operation_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn in_one_level_operation_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -161,7 +149,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn next_crosses_operation_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn next_crosses_operation_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -181,7 +169,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn in_multiple_operations_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn in_multiple_operations_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -208,7 +196,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn out_multiple_operations_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn out_multiple_operations_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/tests.rs
similarity index 95%
rename from compiler/qsc/src/interpret/stateful/tests.rs
rename to compiler/qsc/src/interpret/tests.rs
index a885c800..e2706e2a 100644
--- a/compiler/qsc/src/interpret/stateful/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -4,7 +4,7 @@
 #![allow(clippy::needless_raw_string_hashes)]
 
 mod given_interpreter {
-    use crate::interpret::stateful::{Error, InterpretResult, Interpreter};
+    use crate::interpret::{Error, InterpretResult, Interpreter};
     use expect_test::Expect;
     use miette::Diagnostic;
     use qsc_eval::{output::CursorReceiver, val::Value};
@@ -24,19 +24,15 @@ mod given_interpreter {
     fn run(
         interpreter: &mut Interpreter,
         expr: &str,
-        shots: u32,
-    ) -> (Result<Vec<InterpretResult>, Vec<Error>>, String) {
+    ) -> (Result<InterpretResult, Vec<Error>>, String) {
         let mut cursor = Cursor::new(Vec::<u8>::new());
         let mut receiver = CursorReceiver::new(&mut cursor);
-        (interpreter.run(&mut receiver, expr, shots), receiver.dump())
+        (interpreter.run(&mut receiver, expr), receiver.dump())
     }
 
     fn entry(
         interpreter: &mut Interpreter,
-    ) -> (
-        Result<Value, Vec<crate::interpret::stateful::Error>>,
-        String,
-    ) {
+    ) -> (Result<Value, Vec<crate::interpret::Error>>, String) {
         let mut cursor = Cursor::new(Vec::<u8>::new());
         let mut receiver = CursorReceiver::new(&mut cursor);
         (interpreter.eval_entry(&mut receiver), receiver.dump())
@@ -960,47 +956,45 @@ mod given_interpreter {
         #[test]
         fn run_with_shots() {
             let mut interpreter = get_interpreter();
-            let (result, output) = line(&mut interpreter, "operation Foo() : Int { 1 }");
+            let (result, output) = line(&mut interpreter, "operation Foo(qs : Qubit[]) : Unit { Microsoft.Quantum.Diagnostics.DumpMachine(); }");
             is_only_value(&result, &output, &Value::unit());
-            let (results, output) = run(&mut interpreter, "Foo()", 5);
-            assert_eq!(output, String::new());
-            let results = results.expect("run() should succeed");
-            assert_eq!(results.len(), 5);
-            for r in results {
-                let val = r.expect("individual run should succeed");
-                assert_eq!(val, Value::Int(1));
+            for _ in 0..4 {
+                let (results, output) = run(&mut interpreter, "{use qs = Qubit[2]; Foo(qs)}");
+                is_unit_with_output(
+                    &results.expect("compilation should succeed"),
+                    &output,
+                    "STATE:\n|00⟩: 1+0i",
+                );
             }
         }
 
         #[test]
         fn run_parse_error() {
             let mut interpreter = get_interpreter();
-            let (results, _) = run(&mut interpreter, "Foo)", 5);
+            let (results, _) = run(&mut interpreter, "Foo)");
             results.expect_err("run() should fail");
         }
 
         #[test]
         fn run_compile_error() {
             let mut interpreter = get_interpreter();
-            let (results, _) = run(&mut interpreter, "Foo()", 5);
+            let (results, _) = run(&mut interpreter, "Foo()");
             results.expect_err("run() should fail");
         }
 
         #[test]
-        fn run_multiple_statements() {
+        fn run_multiple_statements_with_return_value() {
             let mut interpreter = get_interpreter();
             let (result, output) = line(&mut interpreter, "operation Foo() : Int { 1 }");
             is_only_value(&result, &output, &Value::unit());
             let (result, output) = line(&mut interpreter, "operation Bar() : Int { 2 }");
             is_only_value(&result, &output, &Value::unit());
-            let (results, output) = run(&mut interpreter, "{ Foo(); Bar() }", 5);
-            assert_eq!(output, String::new());
-            let results = results.expect("run() should succeed");
-            assert_eq!(results.len(), 5);
-            for r in results {
-                let val = r.expect("individual run should succeed");
-                assert_eq!(val, Value::Int(2));
-            }
+            let (result, output) = run(&mut interpreter, "{ Foo(); Bar() }");
+            is_only_value(
+                &result.expect("compilation should succeed"),
+                &output,
+                &Value::Int(2),
+            );
         }
 
         #[test]
@@ -1011,12 +1005,16 @@ mod given_interpreter {
                 r#"operation Foo() : Int { fail "failed" }"#,
             );
             is_only_value(&result, &output, &Value::unit());
-            let (results, output) = run(&mut interpreter, "Foo()", 5);
-            assert_eq!(output, String::new());
-            let results = results.expect("run() should succeed");
-            assert_eq!(results.len(), 5);
-            for r in results {
-                r.expect_err("individual run should fail");
+            for _ in 0..1 {
+                let (result, output) = run(&mut interpreter, "Foo()");
+                is_only_error(
+                    &result.expect("compilation should succeed"),
+                    &output,
+                    &expect![[r#"
+                        runtime error: program failed: failed
+                          explicit fail [line_0] [fail "failed"]
+                    "#]],
+                );
             }
         }
 
@@ -1028,19 +1026,13 @@ mod given_interpreter {
                 r#"operation Foo() : Unit { Message("hello!") }"#,
             );
             is_only_value(&result, &output, &Value::unit());
-            let (results, output) = run(&mut interpreter, "Foo()", 5);
-            expect![[r#"
-                hello!
-                hello!
-                hello!
-                hello!
-                hello!"#]]
-            .assert_eq(&output);
-            let results = results.expect("run() should succeed");
-            assert_eq!(results.len(), 5);
-            for r in results {
-                let val = r.expect("individual run should succeed");
-                assert_eq!(val, Value::unit());
+            for _ in 0..4 {
+                let (result, output) = run(&mut interpreter, "Foo()");
+                is_unit_with_output(
+                    &result.expect("compilation should succeed"),
+                    &output,
+                    "hello!",
+                );
             }
         }
     }
@@ -1065,7 +1057,7 @@ mod given_interpreter {
     }
 
     fn is_unit_with_output_eval_entry(
-        result: &Result<Value, Vec<crate::interpret::stateful::Error>>,
+        result: &Result<Value, Vec<crate::interpret::Error>>,
         output: &str,
         expected_output: &str,
     ) {
diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs
index 2b3c7f1e..4c05191d 100644
--- a/compiler/qsc_eval/src/intrinsic/tests.rs
+++ b/compiler/qsc_eval/src/intrinsic/tests.rs
@@ -134,10 +134,6 @@ impl Backend for CustomSim {
         self.sim.qubit_is_zero(q)
     }
 
-    fn reinit(&mut self) {
-        self.sim.reinit();
-    }
-
     fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
         match name {
             "Add1" => Some(Ok(Value::Int(arg.unwrap_int() + 1))),
diff --git a/compiler/qsc_frontend/src/lower/tests.rs b/compiler/qsc_frontend/src/lower/tests.rs
index c9b403b6..47ae433e 100644
--- a/compiler/qsc_frontend/src/lower/tests.rs
+++ b/compiler/qsc_frontend/src/lower/tests.rs
@@ -134,7 +134,7 @@ fn test_target_profile_attr_wrong_args() {
         &expect![[r#"
             [
                 InvalidAttrArgs(
-                    "Full or Base",
+                    "Unrestricted or Base",
                     Span {
                         lo: 29,
                         hi: 34,
diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs
index a94f3214..d16bc316 100644
--- a/compiler/qsc_frontend/src/typeck/tests.rs
+++ b/compiler/qsc_frontend/src/typeck/tests.rs
@@ -1181,7 +1181,7 @@ fn ternop_update_array_range_takes_array() {
             }
         "},
         "",
-        &expect![[r##"
+        &expect![[r#"
             #6 30-32 "()" : Unit
             #8 38-112 "{\n        let xs = [0, 1, 2];\n        let ys = xs w/ 0..1 <- [3, 4];\n    }" : Unit
             #10 52-54 "xs" : Int[]
@@ -1198,7 +1198,7 @@ fn ternop_update_array_range_takes_array() {
             #26 99-105 "[3, 4]" : Int[]
             #27 100-101 "3" : Int
             #28 103-104 "4" : Int
-        "##]],
+        "#]],
     );
 }
 
@@ -1214,7 +1214,7 @@ fn ternop_update_array_range_with_non_array_error() {
             }
         "},
         "",
-        &expect![[r##"
+        &expect![[r#"
             #6 30-32 "()" : Unit
             #8 38-107 "{\n        let xs = [0, 1, 2];\n        let ys = xs w/ 0..1 <- 3;\n    }" : Unit
             #10 52-54 "xs" : Int[]
@@ -1230,7 +1230,7 @@ fn ternop_update_array_range_with_non_array_error() {
             #25 94-95 "1" : Int
             #26 99-100 "3" : Int
             Error(Type(Error(TyMismatch("Int[]", "Int", Span { lo: 85, hi: 100 }))))
-        "##]],
+        "#]],
     );
 }
 
@@ -3588,3 +3588,45 @@ fn inference_infinite_recursion_should_fail() {
         "#]],
     );
 }
+
+#[test]
+fn use_range_field_names_in_udt() {
+    check(
+        indoc! {"
+            namespace A {
+                newtype B = (
+                    Start : Int,
+                    Step : Int,
+                    End : Int
+                );
+                function C() : Unit {
+                    let udt = B(1, 2, 3);
+                    let start = udt::Start;
+                    let step = udt::Step;
+                    let end = udt::End;
+                }
+            }
+        "},
+        "",
+        &expect![[r#"
+            #24 112-114 "()" : Unit
+            #28 122-249 "{\n        let udt = B(1, 2, 3);\n        let start = udt::Start;\n        let step = udt::Step;\n        let end = udt::End;\n    }" : Unit
+            #30 136-139 "udt" : UDT<"B": Item 1>
+            #32 142-152 "B(1, 2, 3)" : UDT<"B": Item 1>
+            #33 142-143 "B" : ((Int, Int, Int) -> UDT<"B": Item 1>)
+            #36 143-152 "(1, 2, 3)" : (Int, Int, Int)
+            #37 144-145 "1" : Int
+            #38 147-148 "2" : Int
+            #39 150-151 "3" : Int
+            #41 166-171 "start" : Int
+            #43 174-184 "udt::Start" : Int
+            #44 174-177 "udt" : UDT<"B": Item 1>
+            #49 198-202 "step" : Int
+            #51 205-214 "udt::Step" : Int
+            #52 205-208 "udt" : UDT<"B": Item 1>
+            #57 228-231 "end" : Int
+            #59 234-242 "udt::End" : Int
+            #60 234-237 "udt" : UDT<"B": Item 1>
+        "#]],
+    );
+}
diff --git a/katas/src/tests.rs b/katas/src/tests.rs
index a35c9e61..a94b38c9 100644
--- a/katas/src/tests.rs
+++ b/katas/src/tests.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use qsc::interpret::{output::CursorReceiver, stateful};
+use qsc::interpret::{output::CursorReceiver, Error};
 use std::{
     env, fs,
     io::Cursor,
@@ -14,7 +14,7 @@ fn test_cases_dir() -> PathBuf {
         .join("test_cases")
 }
 
-fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<stateful::Error>> {
+fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<Error>> {
     let mut cursor = Cursor::new(Vec::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     let result = crate::check_solution(
diff --git a/library/tests/Cargo.toml b/library/Cargo.toml
similarity index 78%
rename from library/tests/Cargo.toml
rename to library/Cargo.toml
index a2ecf62d..a635254a 100644
--- a/library/tests/Cargo.toml
+++ b/library/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "stdlib_tests"
+name = "library"
 
 version.workspace = true
 authors.workspace = true
@@ -8,12 +8,10 @@ repository.workspace = true
 edition.workspace = true
 license.workspace = true
 
-[dependencies]
-num-bigint = { workspace = true }
-qsc = { path = "../../compiler/qsc" }
-
 [dev-dependencies]
 indoc = { workspace = true }
+num-bigint = { workspace = true }
+qsc = { path = "../compiler/qsc" }
 
 [lib]
 doctest = false
diff --git a/library/tests/src/tests.rs b/library/src/tests.rs
similarity index 50%
rename from library/tests/src/tests.rs
rename to library/src/tests.rs
index 177c6856..a50e191e 100644
--- a/library/tests/src/tests.rs
+++ b/library/src/tests.rs
@@ -3,9 +3,86 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::{test_expression, test_expression_with_lib_and_profile};
+mod arithmetic;
+mod arrays;
+mod canon;
+mod convert;
+mod diagnostics;
+mod math;
+mod measurement;
+mod table_lookup;
+
 use indoc::indoc;
-use qsc::{interpret::Value, target::Profile};
+use qsc::{
+    interpret::{GenericReceiver, Interpreter, Value},
+    target::Profile,
+    PackageType, SourceMap,
+};
+
+/// # Panics
+///
+/// Will panic if compilation fails or the result is not the same as expected.
+/// NOTE: Floating point numbers in tuples are compared taking precision into
+/// account so that results of calculations can also be compared.
+pub fn test_expression(expr: &str, expected: &Value) {
+    test_expression_with_lib(expr, "", expected);
+}
+
+pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) {
+    test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected);
+}
+
+pub fn test_expression_with_lib_and_profile(
+    expr: &str,
+    lib: &str,
+    profile: Profile,
+    expected: &Value,
+) {
+    let mut stdout = vec![];
+    let mut out = GenericReceiver::new(&mut stdout);
+
+    let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into()));
+
+    let mut interpreter = Interpreter::new(true, sources, PackageType::Exe, profile.into())
+        .expect("test should compile");
+    let result = interpreter
+        .eval_entry(&mut out)
+        .expect("test should run successfully");
+
+    match (&expected, result) {
+        (&Value::Tuple(tup1), Value::Tuple(tup2)) if tup1.len() == tup2.len() => {
+            // If both values are tuples of the same length, we crack them open and compare elements
+            for (value1, value2) in tup1.iter().zip(tup2.iter()) {
+                if let (Value::Double(double1), Value::Double(double2)) = (value1, value2) {
+                    // If both elements are doubles, we use approximate comparison
+                    assert_doubles_almost_equal(*double1, *double2);
+                } else {
+                    assert_eq!(value1, value2);
+                }
+            }
+        }
+        (&Value::Double(double1), Value::Double(double2)) => {
+            assert_doubles_almost_equal(*double1, double2);
+        }
+        (&expected, result) => assert_eq!(expected, &result),
+    }
+}
+
+/// # Panics
+///
+/// Will panic if f64 values are significantly different.
+fn assert_doubles_almost_equal(val1: f64, val2: f64) {
+    let val1_abs = val1.abs();
+    let val2_abs = val2.abs();
+    if val1_abs < f64::MIN_POSITIVE && val2_abs < f64::MIN_POSITIVE {
+        // Note, that f64::MIN_POSITIVE is not the smallest representable positive number.
+        return;
+    }
+    assert!(
+        ((val1 - val2).abs() / (val1_abs + val2_abs)) < 1e-15,
+        "Significant difference between expected and actual values: val1={val1}, val2={val2}."
+    );
+}
 
 //
 // Core namespace
diff --git a/library/tests/src/test_arithmetic.rs b/library/src/tests/arithmetic.rs
similarity index 99%
rename from library/tests/src/test_arithmetic.rs
rename to library/src/tests/arithmetic.rs
index 2eef74fe..f25209b7 100644
--- a/library/tests/src/test_arithmetic.rs
+++ b/library/src/tests/arithmetic.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{test_expression, test_expression_with_lib};
+use super::{test_expression, test_expression_with_lib};
 use qsc::interpret::Value;
 
 // Tests for Microsoft.Quantum.Unstable.Arithmetic namespace
diff --git a/library/tests/src/test_arrays.rs b/library/src/tests/arrays.rs
similarity index 99%
rename from library/tests/src/test_arrays.rs
rename to library/src/tests/arrays.rs
index 8e44c423..9628f939 100644
--- a/library/tests/src/test_arrays.rs
+++ b/library/src/tests/arrays.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::test_expression;
+use super::test_expression;
 use num_bigint::BigInt;
 use qsc::interpret::Value;
 
diff --git a/library/tests/src/test_canon.rs b/library/src/tests/canon.rs
similarity index 99%
rename from library/tests/src/test_canon.rs
rename to library/src/tests/canon.rs
index f70b9123..4d0c158f 100644
--- a/library/tests/src/test_canon.rs
+++ b/library/src/tests/canon.rs
@@ -3,7 +3,7 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::{test_expression, test_expression_with_lib};
+use super::{test_expression, test_expression_with_lib};
 use indoc::indoc;
 use qsc::interpret::Value;
 
diff --git a/library/tests/src/test_convert.rs b/library/src/tests/convert.rs
similarity index 99%
rename from library/tests/src/test_convert.rs
rename to library/src/tests/convert.rs
index 428663de..a194e78f 100644
--- a/library/tests/src/test_convert.rs
+++ b/library/src/tests/convert.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::test_expression;
+use super::test_expression;
 use qsc::interpret::Value;
 
 // Tests for Microsoft.Quantum.Convert namespace
diff --git a/library/tests/src/test_diagnostics.rs b/library/src/tests/diagnostics.rs
similarity index 97%
rename from library/tests/src/test_diagnostics.rs
rename to library/src/tests/diagnostics.rs
index 240ace1a..7aed5a90 100644
--- a/library/tests/src/test_diagnostics.rs
+++ b/library/src/tests/diagnostics.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::test_expression;
+use super::test_expression;
 use qsc::interpret::Value;
 
 #[test]
diff --git a/library/tests/src/test_math.rs b/library/src/tests/math.rs
similarity index 99%
rename from library/tests/src/test_math.rs
rename to library/src/tests/math.rs
index f9651352..61becf42 100644
--- a/library/tests/src/test_math.rs
+++ b/library/src/tests/math.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::test_expression;
+use super::test_expression;
 use core::f64::consts::E;
 use num_bigint::BigInt;
 use qsc::interpret::Value;
diff --git a/library/tests/src/test_measurement.rs b/library/src/tests/measurement.rs
similarity index 99%
rename from library/tests/src/test_measurement.rs
rename to library/src/tests/measurement.rs
index 8923e207..8b36185f 100644
--- a/library/tests/src/test_measurement.rs
+++ b/library/src/tests/measurement.rs
@@ -3,7 +3,7 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::test_expression;
+use super::test_expression;
 use indoc::indoc;
 use qsc::interpret::Value;
 
diff --git a/library/tests/src/resources/add_le.qs b/library/src/tests/resources/add_le.qs
similarity index 100%
rename from library/tests/src/resources/add_le.qs
rename to library/src/tests/resources/add_le.qs
diff --git a/library/tests/src/resources/compare.qs b/library/src/tests/resources/compare.qs
similarity index 100%
rename from library/tests/src/resources/compare.qs
rename to library/src/tests/resources/compare.qs
diff --git a/library/tests/src/resources/inc_by_le.qs b/library/src/tests/resources/inc_by_le.qs
similarity index 100%
rename from library/tests/src/resources/inc_by_le.qs
rename to library/src/tests/resources/inc_by_le.qs
diff --git a/library/tests/src/resources/qft_le.qs b/library/src/tests/resources/qft_le.qs
similarity index 100%
rename from library/tests/src/resources/qft_le.qs
rename to library/src/tests/resources/qft_le.qs
diff --git a/library/tests/src/resources/select.qs b/library/src/tests/resources/select.qs
similarity index 100%
rename from library/tests/src/resources/select.qs
rename to library/src/tests/resources/select.qs
diff --git a/library/tests/src/test_table_lookup.rs b/library/src/tests/table_lookup.rs
similarity index 96%
rename from library/tests/src/test_table_lookup.rs
rename to library/src/tests/table_lookup.rs
index dfad1fbf..9f8563b0 100644
--- a/library/tests/src/test_table_lookup.rs
+++ b/library/src/tests/table_lookup.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::test_expression_with_lib;
+use super::test_expression_with_lib;
 use qsc::interpret::Value;
 
 // Tests for Microsoft.Quantum.TableLookup namespace
diff --git a/library/tests/src/lib.rs b/library/tests/src/lib.rs
deleted file mode 100644
index bf785e30..00000000
--- a/library/tests/src/lib.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-#![warn(clippy::mod_module_files, clippy::pedantic, clippy::unwrap_used)]
-#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
-
-#[cfg(test)]
-mod test_arithmetic;
-#[cfg(test)]
-mod test_arrays;
-#[cfg(test)]
-mod test_canon;
-#[cfg(test)]
-mod test_convert;
-#[cfg(test)]
-mod test_diagnostics;
-#[cfg(test)]
-mod test_math;
-#[cfg(test)]
-mod test_measurement;
-#[cfg(test)]
-mod test_table_lookup;
-#[cfg(test)]
-mod tests;
-
-use qsc::{
-    interpret::{stateful, GenericReceiver, Value},
-    target::Profile,
-    PackageType, SourceMap,
-};
-
-/// # Panics
-///
-/// Will panic if compilation fails or the result is not the same as expected.
-/// NOTE: Floating point numbers in tuples are compared taking precision into
-/// account so that results of calculations can also be compared.
-pub fn test_expression(expr: &str, expected: &Value) {
-    test_expression_with_lib(expr, "", expected);
-}
-
-pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) {
-    test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected);
-}
-
-pub fn test_expression_with_lib_and_profile(
-    expr: &str,
-    lib: &str,
-    profile: Profile,
-    expected: &Value,
-) {
-    let mut stdout = vec![];
-    let mut out = GenericReceiver::new(&mut stdout);
-
-    let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into()));
-
-    let mut interpreter =
-        stateful::Interpreter::new(true, sources, PackageType::Exe, profile.into())
-            .expect("test should compile");
-    let result = interpreter
-        .eval_entry(&mut out)
-        .expect("test should run successfully");
-
-    match (&expected, result) {
-        (&Value::Tuple(tup1), Value::Tuple(tup2)) if tup1.len() == tup2.len() => {
-            // If both values are tuples of the same length, we crack them open and compare elements
-            for (value1, value2) in tup1.iter().zip(tup2.iter()) {
-                if let (Value::Double(double1), Value::Double(double2)) = (value1, value2) {
-                    // If both elements are doubles, we use approximate comparison
-                    assert_doubles_almost_equal(*double1, *double2);
-                } else {
-                    assert_eq!(value1, value2);
-                }
-            }
-        }
-        (&Value::Double(double1), Value::Double(double2)) => {
-            assert_doubles_almost_equal(*double1, double2);
-        }
-        (&expected, result) => assert_eq!(expected, &result),
-    }
-}
-
-/// # Panics
-///
-/// Will panic if f64 values are significantly different.
-fn assert_doubles_almost_equal(val1: f64, val2: f64) {
-    let val1_abs = val1.abs();
-    let val2_abs = val2.abs();
-    if val1_abs < f64::MIN_POSITIVE && val2_abs < f64::MIN_POSITIVE {
-        // Note, that f64::MIN_POSITIVE is not the smallest representable positive number.
-        return;
-    }
-    assert!(
-        ((val1 - val2).abs() / (val1_abs + val2_abs)) < 1e-15,
-        "Significant difference between expected and actual values: val1={val1}, val2={val2}."
-    );
-}
diff --git a/npm/test/basics.js b/npm/test/basics.js
index 714587e8..0245dbba 100644
--- a/npm/test/basics.js
+++ b/npm/test/basics.js
@@ -16,6 +16,7 @@ import {
 import { QscEventTarget } from "../dist/compiler/events.js";
 import { getAllKatas, getExerciseSources, getKata } from "../dist/katas.js";
 import samples from "../dist/samples.generated.js";
+import { CreateIntegerTicks, CreateTimeTicks } from "../dist/ux/ticks.js";
 
 /** @type {import("../dist/log.js").TelemetryEvent[]} */
 const telemetryEvents = [];
@@ -879,3 +880,269 @@ test("debug service compiling multiple sources - web worker", async () => {
     debugService.terminate();
   }
 });
+
+test("CreateIntegerTicks: invalid inputs", () => {
+  runAndAssertIntegerTicks(2, 1, []);
+  runAndAssertIntegerTicks(0, 2, []);
+  runAndAssertIntegerTicks(-5, 100, []);
+});
+
+test("CreateIntegerTicks: below 100", () => {
+  runAndAssertIntegerTicks(1, 1, [1]);
+  runAndAssertIntegerTicks(3, 3, [3]);
+  runAndAssertIntegerTicks(4, 6, [4, 5, 6]);
+  runAndAssertIntegerTicks(1, 100, [1, 10, 100]);
+  runAndAssertIntegerTicks(1, 10, [1, 10]);
+  runAndAssertIntegerTicks(1, 9, [1]);
+  runAndAssertIntegerTicks(2, 10, [10]);
+  runAndAssertIntegerTicks(2, 9, [2, 3, 4, 5, 6, 7, 8, 9]);
+});
+
+test("CreateIntegerTicks: more than 100", () => {
+  runAndAssertIntegerTicks(20, 59, [20, 30, 40, 50]);
+  runAndAssertIntegerTicks(231, 365, [300]);
+  runAndAssertIntegerTicks(331, 365, [340, 350, 360]);
+  runAndAssertIntegerTicks(567, 569, [567, 568, 569]);
+});
+
+test("CreateIntegerTicks: expected qubit numbers", () => {
+  runAndAssertIntegerTicks(400, 8000000, [1000, 10000, 100000, 1000000]);
+  runAndAssertIntegerTicks(12345, 67890, [20000, 30000, 40000, 50000, 60000]);
+  runAndAssertIntegerTicks(23456, 27890, [24000, 25000, 26000, 27000]);
+});
+
+test("CreateTimeTicks: invalid inputs", () => {
+  runAndAssertTimeTicks(2, 1, []);
+  runAndAssertTimeTicks(0, 2, []);
+  runAndAssertTimeTicks(-5, 100, []);
+});
+
+const second = 1e9;
+const minute = 60 * second;
+const hour = 60 * minute;
+const day = 24 * hour;
+const week = 7 * day;
+const month = 30 * day;
+const year = 365 * day;
+const decade = 10 * year;
+const century = 10 * decade;
+
+test("CreateTimeTicks: nanoseconds below 100", () => {
+  runAndAssertTimeTicks(1, 1, ["1 nanosecond"]);
+  runAndAssertTimeTicks(3, 3, ["3 nanoseconds"]);
+  runAndAssertTimeTicks(4, 6, [
+    "4 nanoseconds",
+    "5 nanoseconds",
+    "6 nanoseconds",
+  ]);
+  runAndAssertTimeTicks(1, 100, ["1 nanosecond"]);
+  runAndAssertTimeTicks(1, 10, ["1 nanosecond"]);
+  runAndAssertTimeTicks(1, 9, ["1 nanosecond"]);
+  runAndAssertTimeTicks(2, 10, ["10 nanoseconds"]);
+  runAndAssertTimeTicks(2, 9, [
+    "2 nanoseconds",
+    "3 nanoseconds",
+    "4 nanoseconds",
+    "5 nanoseconds",
+    "6 nanoseconds",
+    "7 nanoseconds",
+    "8 nanoseconds",
+    "9 nanoseconds",
+  ]);
+});
+
+test("CreateTimeTicks: microseconds", () => {
+  runAndAssertTimeTicks(800, 1000, ["1 microsecond"]);
+  runAndAssertTimeTicks(800, 2000, ["1 microsecond"]);
+  runAndAssertTimeTicks(800, 11000, ["1 microsecond"]);
+  runAndAssertTimeTicks(800, 21000, ["1 microsecond"]);
+  runAndAssertTimeTicks(800, 111000, ["1 microsecond"]);
+  runAndAssertTimeTicks(1001, 21000, ["10 microseconds"]);
+  runAndAssertTimeTicks(10001, 21000, ["20 microseconds"]);
+  runAndAssertTimeTicks(10001, 30000, ["20 microseconds", "30 microseconds"]);
+});
+
+test("CreateTimeTicks: milliseconds", () => {
+  runAndAssertTimeTicks(800, 999999, ["1 microsecond"]);
+  runAndAssertTimeTicks(800, 1000000, ["1 microsecond", "1 millisecond"]);
+  runAndAssertTimeTicks(800000, 2000000, ["1 millisecond"]);
+  runAndAssertTimeTicks(800000, 11000000, ["1 millisecond"]);
+  runAndAssertTimeTicks(800000, 21000000, ["1 millisecond"]);
+  runAndAssertTimeTicks(800000, 111000000, ["1 millisecond"]);
+  runAndAssertTimeTicks(1000001, 111000000, ["100 milliseconds"]);
+});
+
+test("CreateTimeTicks: seconds", () => {
+  runAndAssertTimeTicks(800000, second - 1, ["1 millisecond"]);
+  runAndAssertTimeTicks(800000, second, ["1 millisecond", "1 second"]);
+  runAndAssertTimeTicks(800000000, 2 * second, ["1 second"]);
+  runAndAssertTimeTicks(800000000, 11 * second, ["1 second"]);
+  runAndAssertTimeTicks(800000000, 21 * second, ["1 second"]);
+  runAndAssertTimeTicks(800000000, 111 * second, ["1 second", "1 minute"]);
+  runAndAssertTimeTicks(second + 1, 111 * second, ["1 minute"]);
+});
+
+test("CreateTimeTicks: minutes", () => {
+  runAndAssertTimeTicks(second - 1, minute, ["1 second", "1 minute"]);
+  runAndAssertTimeTicks(minute - second, 2 * minute, ["1 minute"]);
+  runAndAssertTimeTicks(minute, 11 * minute, ["1 minute"]);
+  runAndAssertTimeTicks(minute + 1, 21 * minute, ["10 minutes"]);
+  runAndAssertTimeTicks(second, 111 * minute, [
+    "1 second",
+    "1 minute",
+    "1 hour",
+  ]);
+  runAndAssertTimeTicks(minute + 1, 111 * minute, ["1 hour"]);
+});
+
+test("CreateTimeTicks: hours", () => {
+  runAndAssertTimeTicks(minute - 1, hour, ["1 minute", "1 hour"]);
+  runAndAssertTimeTicks(hour - minute, 2 * hour, ["1 hour"]);
+  runAndAssertTimeTicks(hour, 11 * hour, ["1 hour"]);
+  runAndAssertTimeTicks(hour + 1, 21 * hour, ["10 hours"]);
+  runAndAssertTimeTicks(minute, 111 * hour, ["1 minute", "1 hour", "1 day"]);
+  runAndAssertTimeTicks(hour + 1, 111 * hour, ["1 day"]);
+});
+
+test("CreateTimeTicks: days", () => {
+  runAndAssertTimeTicks(hour - 1, day, ["1 hour", "1 day"]);
+  runAndAssertTimeTicks(day - hour, 2 * day, ["1 day"]);
+  runAndAssertTimeTicks(day, 11 * day, ["1 day", "1 week"]);
+  runAndAssertTimeTicks(day + 1, 21 * day, ["1 week"]);
+  runAndAssertTimeTicks(hour, 111 * day, [
+    "1 hour",
+    "1 day",
+    "1 week",
+    "1 month",
+  ]);
+  runAndAssertTimeTicks(day + 1, 111 * day, ["1 week", "1 month"]);
+});
+
+test("CreateTimeTicks: weeks", () => {
+  runAndAssertTimeTicks(day, week, ["1 day", "1 week"]);
+  runAndAssertTimeTicks(day + 1, week, ["1 week"]);
+  runAndAssertTimeTicks(day * 8, day * 27, ["2 weeks", "3 weeks"]);
+  runAndAssertTimeTicks(week - day, 2 * week, ["1 week"]);
+  runAndAssertTimeTicks(week, 11 * week, ["1 week", "1 month"]);
+  runAndAssertTimeTicks(week + 1, 35 * week, ["1 month"]);
+  runAndAssertTimeTicks(day, 111 * week, [
+    "1 day",
+    "1 week",
+    "1 month",
+    "1 year",
+  ]);
+  runAndAssertTimeTicks(week + 1, 111 * week, ["1 month", "1 year"]);
+});
+
+test("CreateTimeTicks: months", () => {
+  runAndAssertTimeTicks(week - 1, month, ["1 week", "1 month"]);
+  runAndAssertTimeTicks(month - 1, 2 * month, ["1 month"]);
+  runAndAssertTimeTicks(month, 11 * month, ["1 month"]);
+  runAndAssertTimeTicks(month, 12 * month, ["1 month"]);
+  runAndAssertTimeTicks(month, 12 * month + 5 * day, ["1 month", "1 year"]);
+  runAndAssertTimeTicks(month + 1, 12 * month, ["10 months"]);
+  // due to precision issues month + 1 == month
+  runAndAssertTimeTicks(month + hour, 10 * month - hour, [
+    "2 months",
+    "3 months",
+    "4 months",
+    "5 months",
+    "6 months",
+    "7 months",
+    "8 months",
+    "9 months",
+  ]);
+  runAndAssertTimeTicks(week, 111 * month, ["1 week", "1 month", "1 year"]);
+  runAndAssertTimeTicks(month + 1, 111 * month, ["1 year"]);
+});
+
+test("CreateTimeTicks: years", () => {
+  runAndAssertTimeTicks(month - 1, year, ["1 month", "1 year"]);
+  runAndAssertTimeTicks(year - month, 2 * year, ["1 year"]);
+  // due to precision issues year + 1 == year and decade - 1 == decade
+  runAndAssertTimeTicks(year + day, decade - day, [
+    "2 years",
+    "3 years",
+    "4 years",
+    "5 years",
+    "6 years",
+    "7 years",
+    "8 years",
+    "9 years",
+  ]);
+
+  runAndAssertTimeTicks(month, 111 * year, [
+    "1 month",
+    "1 year",
+    "1 decade",
+    "1 century",
+  ]);
+});
+
+test("CreateTimeTicks: decades", () => {
+  // due to precision issues year + 1 == year
+  runAndAssertTimeTicks(year + day, 21 * year, ["1 decade"]);
+  runAndAssertTimeTicks(year, decade, ["1 year", "1 decade"]);
+  runAndAssertTimeTicks(decade - year, 2 * decade, ["1 decade"]);
+  runAndAssertTimeTicks(year, 111 * decade, [
+    "1 year",
+    "1 decade",
+    "1 century",
+  ]);
+  // due to precision issues decade + 1 == decade
+  runAndAssertTimeTicks(decade + month, 111 * decade, ["1 century"]);
+});
+
+test("CreateTimeTicks: centuries", () => {
+  runAndAssertTimeTicks(decade - 1, century, ["1 decade", "1 century"]);
+  runAndAssertTimeTicks(century - decade, 2 * century, ["1 century"]);
+  runAndAssertTimeTicks(century, 11 * century, ["1 century"]);
+  runAndAssertTimeTicks(century + 1, 21 * century, ["1 century"]);
+  runAndAssertTimeTicks(decade, 111 * century, ["1 decade", "1 century"]);
+  runAndAssertTimeTicks(century + 1, 111 * century, ["1 century"]);
+});
+
+test("CreateTimeTicks: above centuries", () => {
+  runAndAssertTimeTicks(century + 30 * year, 3 * century, [
+    "2 centuries",
+    "3 centuries",
+  ]);
+  runAndAssertTimeTicks(century + 30 * year, century + 55 * year, [
+    "13 decades",
+    "14 decades",
+    "15 decades",
+  ]);
+  runAndAssertTimeTicks(2 * century + 32 * year, 2 * century + 36 * year, [
+    "232 years",
+    "233 years",
+    "234 years",
+    "235 years",
+    "236 years",
+  ]);
+});
+
+function getValues(ticks) {
+  return ticks.map((tick) => tick.value);
+}
+
+function getLabels(ticks) {
+  return ticks.map((tick) => tick.label);
+}
+
+function runAndAssertIntegerTicks(min, max, expected) {
+  const message = `min: ${min}, max: ${max}`;
+  assert.deepStrictEqual(
+    getValues(CreateIntegerTicks(min, max)),
+    expected,
+    message,
+  );
+}
+
+function runAndAssertTimeTicks(min, max, expected) {
+  const message = `min: ${min}, max: ${max}`;
+  assert.deepStrictEqual(
+    getLabels(CreateTimeTicks(min, max)),
+    expected,
+    message,
+  );
+}
diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py
index e3ef98ae..87709a2a 100644
--- a/pip/tests/test_interpreter.py
+++ b/pip/tests/test_interpreter.py
@@ -199,7 +199,9 @@ def test_run_with_shots() -> None:
     e.interpret('operation Foo() : Unit { Message("Hello, world!"); }', callback)
     assert called == 0
 
-    value = e.run("Foo()", 5, callback)
+    value = []
+    for _ in range(5):
+        value.append(e.run("Foo()", callback))
     assert called == 5
 
     assert value == [None, None, None, None, None]
diff --git a/pip/tests/test_re.py b/pip/tests/test_re.py
index 748aa6d7..ace1e685 100644
--- a/pip/tests/test_re.py
+++ b/pip/tests/test_re.py
@@ -107,8 +107,14 @@ def test_qsharp_estimation_with_multiple_params() -> None:
     params.items[2].error_budget = 0.333
     assert params.as_dict() == {
         "items": [
-            {"qubitParams": {"name": "qubit_gate_us_e3"}, "errorBudget": 0.333},
-            {"qubitParams": {"name": "qubit_gate_us_e4"}, "errorBudget": 0.333},
+            {
+                "qubitParams": {"name": "qubit_gate_us_e3"},
+                "errorBudget": 0.333,
+            },
+            {
+                "qubitParams": {"name": "qubit_gate_us_e4"},
+                "errorBudget": 0.333,
+            },
             {
                 "qubitParams": {"name": "qubit_maj_ns_e6"},
                 "qecScheme": {"name": "floquet_code"},
diff --git a/resource_estimator/src/counts/tests.rs b/resource_estimator/src/counts/tests.rs
index 491c9bc8..dc0212c1 100644
--- a/resource_estimator/src/counts/tests.rs
+++ b/resource_estimator/src/counts/tests.rs
@@ -8,7 +8,7 @@ use std::convert::Into;
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use qsc::{
-    interpret::{stateful::Interpreter, GenericReceiver},
+    interpret::{GenericReceiver, Interpreter},
     target::Profile,
     PackageType, SourceMap,
 };