about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2023-04-20T10·40+0300
committertazjin <tazjin@tvl.su>2023-05-27T11·40+0000
commit99c78966372017cb3b7bf021f08520a040004b0c (patch)
treeba5d71df0dcd7a662d19525254648106e5da9e94
parent3b33c19a9c43cf66c2d28ffa3d49bb6e8757d9b1 (diff)
feat(corp/rih): implement initial frontend application r/6208
This doesn't actually submit anything to the (not-yet-existing)
backend, but will help the designers figure out what we're actually
looking for here.

Change-Id: I680d88151fb0706953f18eb6256da6f205da7ffb
Reviewed-on: https://cl.tvl.fyi/c/depot/+/8489
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
-rw-r--r--corp/rih/.gitignore3
-rw-r--r--corp/rih/Cargo.lock1337
-rw-r--r--corp/rih/Cargo.toml36
-rw-r--r--corp/rih/README.md3
-rw-r--r--corp/rih/default.nix52
-rw-r--r--corp/rih/index.css62
-rw-r--r--corp/rih/index.html17
-rw-r--r--corp/rih/rih-logo.pngbin0 -> 420956 bytes
-rw-r--r--corp/rih/src/home.html178
-rw-r--r--corp/rih/src/main.rs369
10 files changed, 2057 insertions, 0 deletions
diff --git a/corp/rih/.gitignore b/corp/rih/.gitignore
new file mode 100644
index 0000000000..64476c05e0
--- /dev/null
+++ b/corp/rih/.gitignore
@@ -0,0 +1,3 @@
+result
+target
+dist
diff --git a/corp/rih/Cargo.lock b/corp/rih/Cargo.lock
new file mode 100644
index 0000000000..c0a3a99baa
--- /dev/null
+++ b/corp/rih/Cargo.lock
@@ -0,0 +1,1337 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anymap2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "boolinator"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "csv"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-io"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gloo"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20"
+dependencies = [
+ "gloo-console",
+ "gloo-dialogs",
+ "gloo-events",
+ "gloo-file",
+ "gloo-history",
+ "gloo-net",
+ "gloo-render",
+ "gloo-storage",
+ "gloo-timers",
+ "gloo-utils",
+ "gloo-worker",
+]
+
+[[package]]
+name = "gloo-console"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-dialogs"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-events"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-file"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7"
+dependencies = [
+ "futures-channel",
+ "gloo-events",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-history"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7"
+dependencies = [
+ "gloo-events",
+ "gloo-utils",
+ "serde",
+ "serde-wasm-bindgen",
+ "serde_urlencoded",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-render"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-storage"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gloo-utils"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a"
+dependencies = [
+ "anymap2",
+ "bincode",
+ "gloo-console",
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "implicit-clone"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7"
+dependencies = [
+ "indexmap",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.141"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "phf"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pinned"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b"
+dependencies = [
+ "futures",
+ "rustversion",
+ "thiserror",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prettyplease"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prettytable-rs"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
+dependencies = [
+ "csv",
+ "encode_unicode",
+ "is-terminal",
+ "lazy_static",
+ "term",
+ "unicode-width",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prokio"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488"
+dependencies = [
+ "futures",
+ "gloo",
+ "num_cpus",
+ "once_cell",
+ "pin-project",
+ "pinned",
+ "tokio",
+ "tokio-stream",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "rih"
+version = "0.1.0"
+dependencies = [
+ "fuzzy-matcher",
+ "getrandom",
+ "gloo",
+ "rand",
+ "rust_iso3166",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "wasm-bindgen",
+ "web-sys",
+ "yew",
+ "yew-router",
+]
+
+[[package]]
+name = "route-recognizer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
+
+[[package]]
+name = "rust_iso3166"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e19f23f46e5d2f3f1e917ecf5cc988e23ba7fc2a2ce3ef09cb17033842d0ef8"
+dependencies = [
+ "js-sys",
+ "phf",
+ "prettytable-rs",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-wasm-bindgen"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
+dependencies = [
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tokio"
+version = "1.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
+dependencies = [
+ "autocfg",
+ "pin-project-lite",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[package]]
+name = "web-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "yew"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc"
+dependencies = [
+ "console_error_panic_hook",
+ "futures",
+ "gloo",
+ "implicit-clone",
+ "indexmap",
+ "js-sys",
+ "prokio",
+ "rustversion",
+ "serde",
+ "slab",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "yew-macro",
+]
+
+[[package]]
+name = "yew-macro"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301"
+dependencies = [
+ "boolinator",
+ "once_cell",
+ "prettyplease",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "yew-router"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "426ee0486d2572a6c5e39fbdbc48b58d59bb555f3326f54631025266cf04146e"
+dependencies = [
+ "gloo",
+ "js-sys",
+ "route-recognizer",
+ "serde",
+ "serde_urlencoded",
+ "tracing",
+ "wasm-bindgen",
+ "web-sys",
+ "yew",
+ "yew-router-macro",
+]
+
+[[package]]
+name = "yew-router-macro"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b249cdb39e0cddaf0644dedc781854524374664793479fdc01e6a65d6e6ae3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
diff --git a/corp/rih/Cargo.toml b/corp/rih/Cargo.toml
new file mode 100644
index 0000000000..e624e4e6dc
--- /dev/null
+++ b/corp/rih/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+version = "0.1.0"
+name = "rih"
+authors = [ "Vincent Ambo <tazjin@tvl.su>" ]
+license = "Proprietary"
+edition = "2021"
+
+[dependencies]
+fuzzy-matcher = "0.3.7"
+gloo = "0.8"
+rust_iso3166 = "0.1.10"
+serde_json = "1.0"
+serde_urlencoded = "*" # pinned by yew
+yew = { version = "0.20", features = ["csr"] }
+yew-router = "0.17"
+rand = "0.8"
+getrandom = { version = "0.2", features = ["js"] }
+
+# needs to be in sync with nixpkgs
+wasm-bindgen = "= 0.2.84"
+
+[dependencies.serde]
+version = "*" # pinned by yew
+features = [ "derive" ]
+
+[dependencies.web-sys]
+version = "*" # pinned by yew
+features = [ "HtmlDetailsElement" ]
+
+[profile.release]
+lto = true
+opt-level = 'z'
+codegen-units = 1
+
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = ['-Os']
diff --git a/corp/rih/README.md b/corp/rih/README.md
new file mode 100644
index 0000000000..e44d0f2a58
--- /dev/null
+++ b/corp/rih/README.md
@@ -0,0 +1,3 @@
+Implementation of russiaishiring.com.
+
+This is a corporate TVL project, see `//corp/LICENSE`.
diff --git a/corp/rih/default.nix b/corp/rih/default.nix
new file mode 100644
index 0000000000..24bbde09b9
--- /dev/null
+++ b/corp/rih/default.nix
@@ -0,0 +1,52 @@
+{ lib, pkgs, ... }:
+
+let
+  wasmRust = pkgs.rust-bin.stable.latest.default.override {
+    targets = [ "wasm32-unknown-unknown" ];
+  };
+
+  cargoToml = with builtins; fromTOML (readFile ./Cargo.toml);
+
+  wasmBindgenMatch =
+    cargoToml.dependencies.wasm-bindgen == "= ${pkgs.wasm-bindgen-cli.version}";
+
+  assertWasmBindgen = assert (lib.assertMsg wasmBindgenMatch ''
+    Due to instability in the Rust WASM ecosystem, the trunk build
+    tool enforces that the Cargo-dependency version of `wasm-bindgen`
+    MUST match the version of the CLI supplied in the environment.
+
+    This can get out of sync when nixpkgs is updated. To resolve it,
+    wasm-bindgen must be bumped in the Cargo.toml file and cargo needs
+    to be run to resolve the dependencies.
+
+    Versions of `wasm-bindgen` in Cargo.toml:
+
+      Expected: '= ${pkgs.wasm-bindgen-cli.version}'
+      Actual:   '${cargoToml.dependencies.wasm-bindgen}'
+  ''); pkgs.wasm-bindgen-cli;
+
+  deps = with pkgs; [
+    binaryen
+    sass
+    wasmRust
+    trunk
+    assertWasmBindgen
+  ];
+
+in
+pkgs.rustPlatform.buildRustPackage rec {
+  pname = "rih-frontend";
+  version = "canon";
+  src = lib.cleanSource ./.;
+  cargoLock.lockFile = ./Cargo.lock;
+
+  buildPhase = ''
+    export PATH=${lib.makeBinPath deps}:$PATH
+    mkdir home
+    export HOME=$PWD/.home
+    env
+    trunk build --release -d $out
+  '';
+
+  dontInstall = true;
+}
diff --git a/corp/rih/index.css b/corp/rih/index.css
new file mode 100644
index 0000000000..e71a9c8b0c
--- /dev/null
+++ b/corp/rih/index.css
@@ -0,0 +1,62 @@
+.b-section-divider {
+    width: 100%;
+    height: 3rem;
+    background-color: rgba(0, 0, 0, .1);
+    border: solid rgba(0, 0, 0, .15);
+    border-width: 1px 0;
+    box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
+}
+
+
+/* .btn-light, */
+/* .btn-light:hover, */
+/* .btn-light:focus { */
+/*   color: #333; */
+/*   text-shadow: none; /\* Prevent inheritance from `body` *\/ */
+/* } */
+
+/* body { */
+/*   text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); */
+/*   box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5); */
+/* } */
+
+/* .cover-container { */
+/*   max-width: 42em; */
+/* } */
+
+/* .nav-masthead .nav-link { */
+/*   color: rgba(255, 255, 255, .5); */
+/*   border-bottom: .25rem solid transparent; */
+/* } */
+
+/* .nav-masthead .nav-link:hover, */
+/* .nav-masthead .nav-link:focus { */
+/*   border-bottom-color: rgba(255, 255, 255, .25); */
+/* } */
+
+/* .nav-masthead .nav-link + .nav-link { */
+/*   margin-left: 1rem; */
+/* } */
+
+/* .nav-masthead .active { */
+/*   color: #fff; */
+/*   border-bottom-color: #fff; */
+/* } */
+
+/* body { */
+/*   font-family: Arial, sans-serif; */
+/*   line-height: 1.6; */
+/*   margin: 20px; */
+/*   color: #333; */
+/* } */
+
+/* header { */
+/*   text-align: center; */
+/*   margin-bottom: 30px; */
+/* } */
+
+/* h1 { */
+/*   color: #0039a6; */
+/* } */
+
+/* .intro */
diff --git a/corp/rih/index.html b/corp/rih/index.html
new file mode 100644
index 0000000000..b7d1803f11
--- /dev/null
+++ b/corp/rih/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en" data-bs-theme="auto">
+  <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="theme-color" content="#712cf9">
+    <title>Russia is Hiring</title>
+    <link data-trunk rel="inline" href="index.css">
+    <link data-trunk rel="copy-file" href="rih-logo.png">
+
+    <!-- Bootstrap -->
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/corp/rih/rih-logo.png b/corp/rih/rih-logo.png
new file mode 100644
index 0000000000..5d12843ede
--- /dev/null
+++ b/corp/rih/rih-logo.png
Binary files differdiff --git a/corp/rih/src/home.html b/corp/rih/src/home.html
new file mode 100644
index 0000000000..2098e9a6dd
--- /dev/null
+++ b/corp/rih/src/home.html
@@ -0,0 +1,178 @@
+html! {
+<main>
+  <div class="container px-4 pt-5 my-5 text-center">
+    <div class="row">
+      <div class="col-7 ms-auto">
+        <h1 class="display-5 fw-bold text-body-emphasis">{"Russia is Hiring"}</h1>
+        <p class="lead my-4">
+          {"Are you an IT-specialist on the hunt for a job? Well, "}
+          <a href="https://archive.is/SAONj" class="text-black">{"times are tough"}</a>
+          {" in Western countries at the moment. Meanwhile tech is booming in Russia, and national support programs make life as an IT-specialist very comfortable. Why not look East?"}
+        </p>
+        <p class="lead mb-4">{"We can help you find an employer in Russia, sort out the formalities and get you started. Sign up and tell us a bit about your profile, or read on below about the benefits of life in Russia."}</p>
+
+        <div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
+          <button type="button" class="btn btn-primary btn-lg px-4 gap-3">{"Sign up"}</button>
+        </div>
+      </div>
+      <div class="col-2 me-auto">
+        <img src="/rih-logo.png" height="400px" />
+      </div>
+    </div>
+  </div>
+  <div class="b-section-divider"></div>
+
+  <div class="container px-5 py-5">
+    <h2 class="pb-2 border-bottom">{"Life in Russia"}</h2>
+    <div class="row row-cols-1 row-cols-md-2 align-items-md-center g-5 py-5">
+      <div class="col d-flex flex-column align-items-start gap-2">
+        <h3 class="fw-bold">{"Moscow is very cool and good indeed"}</h3>
+        <p class="text-body-secondary">{"Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words."}</p>
+        <a href="#sign-up" class="btn btn-primary btn-lg">{"Primary button"}</a>
+      </div>
+      <div class="col">
+        <div class="row row-cols-1 row-cols-sm-2 g-4">
+          <div class="col d-flex flex-column gap-2">
+            <div class="feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3">
+            </div>
+            <h4 class="fw-semibold mb-0">{"Look they have banyas"}</h4>
+            <p class="text-body-secondary">{"Paragraph of text beneath the heading to explain the heading."}</p>
+          </div>
+
+          <div class="col d-flex flex-column gap-2">
+            <div class="feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3">
+            </div>
+            <h4 class="fw-semibold mb-0">{"Wow such cultural diversity"}</h4>
+            <p class="text-body-secondary">{"Paragraph of text beneath the heading to explain the heading."}</p>
+          </div>
+
+          <div class="col d-flex flex-column gap-2">
+            <div class="feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3">
+            </div>
+            <h4 class="fw-semibold mb-0">{"Many nice landscapes indeed"}</h4>
+            <p class="text-body-secondary">{"Paragraph of text beneath the heading to explain the heading."}</p>
+          </div>
+
+          <div class="col d-flex flex-column gap-2">
+            <div class="feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3">
+            </div>
+            <h4 class="fw-semibold mb-0">{"And such low taxes!"}</h4>
+            <p class="text-body-secondary">{"Paragraph of text beneath the heading to explain the heading."}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="b-section-divider"></div>
+
+  <div class="container px-4 py-5">
+    <div class="row">
+      <div class="mx-auto col-7">
+        <a id="sign-up"/>
+        <h2 class="pb-2 border-bottom">{"Finding Work in Russia"}</h2>
+        <p>
+          {"Usually landing the most interesting jobs requires you to have a well-developed network of contacts, but this is tough when you set your eyes on a new country. Luckily we at "}
+          <a class="text-black" href={VISTA_URL}>{"Vista Immigration"}</a>
+          {" have contacts with many tech companies in Russia, large and small, and can help you with this!"}</p>
+        <p>{"Tell us a bit about yourself, the technologies you'd like to work with, and your situation in regards to relocating to Russia. We will then match up your profile with companies that match your interests, and establish contact between you and a potential employer if there is a good fit. No generic recruiter spam, guaranteed - we'd rather not send you anything, than send you something irrelevant!"}</p>
+        <p>
+          {"If you get hired, our experts can assist you with legal and other support for your move. Добро пожаловать в Россию!"}
+        </p>
+      </div>
+    </div>
+
+    <div class="row my-3">
+      <div class="col-7 mx-auto">
+        <p>{"Let's get started with a few basic personal details ..."}</p>
+      </div>
+
+      <div class="mx-auto col-6 border rounded-3 shadow">
+        <form class="m-3">
+          <div class="mb-3">
+            <label for="name" class="form-label">{"What's your name?"}</label>
+            <input type="text" class="form-control" id="name"
+                   oninput={link.callback(|event| input_message(event, Msg::SetName))} />
+          </div>
+
+          <div class="mb-3">
+            <label for="email" class="form-label">{"What's your email address?"}</label>
+            <input type="email" class="form-control" id="email" aria-describedby="emailHelp"
+                   oninput={link.callback(|event| input_message(event, Msg::SetEmail))}/>
+            <div id="emailHelp" class="form-text">{"No newsletters, no spam - we will only reach out if there's a match!"}</div>
+          </div>
+
+          <div class="mb-3">
+            <label id="citizenship" class="form-label">{"What citizenship do you hold?"}</label>
+            {citizenship_input(self, link)}
+            <div id="citizenshipHelp" class="form-text">{"We need to know this to estimate immigration-related bureaucracy. If you hold more than one citizenship, pick the one with which you'd want to receive a work visa."}</div>
+          </div>
+
+          <div class="mb-3">
+            <label for="personalDetails" class="form-label">{"Other relevant information:"}</label>
+            <textarea class="form-control" id="personalDetails" rows=3
+                      aria-describedby="personalDetailsHelp"
+                      oninput={link.callback(|event| textarea_message(event, Msg::SetPersonalDetails))} >
+            </textarea>
+            <div id="personalDetailsHelp" class="form-text">{"Any specific places where you'd like to live? Would you be moving with family? Any other assistance required?"}</div>
+          </div>
+
+          <hr />
+          <p>{"Now lets have a look at what you'd like to work with!"}</p>
+
+          <div class="mb-3">
+            <label for="job" class="form-label">{"What job(s) are you looking for?"}</label>
+            <input
+              type="text" class="form-control" id="job"
+              placeholder="Backend/frontend engineer, Test automation, DevOps/SRE, UI/UX ..."
+              oninput={link.callback(|event| input_message(event, Msg::SetPosition))} />
+          </div>
+
+          <div class="mb-3">
+            <label for="technologies" class="form-label">{"Which technologies do you want to work with?"}</label>
+            <div>{render_technologies(link, &self.record.technologies)}</div>
+
+            <input type="text" class="form-control" id="technologies"
+                   aria-describedby="technologiesHelp"
+                   onkeypress={link.callback(add_tech)}/>
+            <div id="technologiesHelp" class="form-text">{"Press enter after each technology."}</div>
+          </div>
+
+          <div class="mb-3">
+            <label for="jobDetails" class="form-label">{"What's your work background?"}</label>
+            <textarea class="form-control" id="workBackground" rows=3
+                      aria-describedby="workBackgroundHelp"
+                      oninput={link.callback(|event| textarea_message(event, Msg::SetWorkBackground))} >
+            </textarea>
+            <div id="workBackgroundHelp" class="form-text">{"Tell us about your work experience, and/or leave links to your CV on your site, LinkedIn or wherever."}</div>
+          </div>
+
+          <div class="mb-3">
+            <label for="jobDetails" class="form-label">{"Other job details:"}</label>
+            <textarea class="form-control" id="jobDetails" rows=3
+                      aria-describedby="jobDetailsHelp"
+                      oninput={link.callback(|event| textarea_message(event, Msg::SetJobDetails))}>
+            </textarea>
+            <div id="jobDetailsHelp" class="form-text">{"Tell us a bit about what you're looking for in a job and in an employer."}</div>
+          </div>
+
+        </form>
+      </div>
+
+    </div>
+  </div>
+  <div class="b-section-divider"></div>
+
+  <footer class="mt-auto text-center">
+    <div class="py-3">
+      <p>
+        {"By "}
+        <a href={VISTA_URL} class="text-black">{"Vista Immigration"}</a>
+        {", with help from "}
+        <a href="https://tvl.su/" class="text-black">{"TVL"}</a>
+        {"."}
+      </p>
+    </div>
+  </footer>
+
+</main>
+}
diff --git a/corp/rih/src/main.rs b/corp/rih/src/main.rs
new file mode 100644
index 0000000000..e2f3480c37
--- /dev/null
+++ b/corp/rih/src/main.rs
@@ -0,0 +1,369 @@
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+use gloo::console;
+use gloo::storage::{LocalStorage, Storage};
+use rand::seq::IteratorRandom;
+use rand::thread_rng;
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeSet;
+use wasm_bindgen::closure::Closure;
+use wasm_bindgen::JsCast;
+use web_sys::{HtmlInputElement, HtmlTextAreaElement, KeyboardEvent};
+use yew::html::Scope;
+use yew::prelude::*;
+
+/// This code ends up being compiled for the native and for the
+/// webassembly architectures during the build & test process.
+/// However, the `rust_iso3166` crate exposes a different API (!)
+/// based on the platform.
+///
+/// This trait acts as a platform-independent wrapper for the crate.
+///
+/// Upstream issue: https://github.com/rust-iso/rust_iso3166/issues/7
+trait CountryCodeAccess {
+    fn country_alpha2(&self) -> String;
+    fn country_alpha3(&self) -> String;
+    fn country_name(&self) -> String;
+}
+
+#[cfg(target_arch = "wasm32")]
+impl CountryCodeAccess for rust_iso3166::CountryCode {
+    fn country_alpha2(&self) -> String {
+        self.alpha2()
+    }
+
+    fn country_alpha3(&self) -> String {
+        self.alpha3()
+    }
+
+    fn country_name(&self) -> String {
+        self.name()
+    }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl CountryCodeAccess for rust_iso3166::CountryCode {
+    fn country_alpha2(&self) -> String {
+        self.alpha2.to_string()
+    }
+
+    fn country_alpha3(&self) -> String {
+        self.alpha3.to_string()
+    }
+
+    fn country_name(&self) -> String {
+        self.name.to_string()
+    }
+}
+
+const VISTA_URL: &'static str = "https://vista-immigration.ru/";
+
+/// Represents a single record as filled in by a user. This is the
+/// primary data structure we want to populate and persist somewhere.
+#[derive(Default, Debug, Deserialize, Serialize)]
+struct Record {
+    // Personal information
+    name: String,
+    email: String,
+    citizenship: String, // TODO
+    personal_details: String,
+
+    // Job information
+    position: String,
+    technologies: BTreeSet<String>,
+    job_details: String,
+    work_background: String,
+}
+
+#[derive(Default)]
+struct App {
+    // The record being populated.
+    record: Record,
+
+    // Is the citizenship input focused?
+    citizenship_focus: bool,
+
+    // Current query in the citizenship field.
+    citizenship_query: String,
+}
+
+#[derive(Clone, Debug)]
+enum Msg {
+    NoOp,
+    AddTechnology(String),
+    RemoveTechnology(String),
+
+    FocusCitizenship,
+    BlurCitizenship,
+    QueryCitizenship(String),
+    SetCitizenship(String),
+
+    SetName(String),
+    SetEmail(String),
+    SetPersonalDetails(String),
+    SetPosition(String),
+    SetJobDetails(String),
+    SetWorkBackground(String),
+}
+
+/// Callback handler for adding a technology.
+fn add_tech(e: KeyboardEvent) -> Msg {
+    if e.key_code() != 13 {
+        return Msg::NoOp;
+    }
+
+    let input = e.target_unchecked_into::<HtmlInputElement>();
+    let tech = input.value();
+    input.set_value("");
+    Msg::AddTechnology(tech)
+}
+
+fn select_country_enter(event: KeyboardEvent) -> Msg {
+    if event.key_code() != 13 {
+        return Msg::NoOp;
+    }
+
+    let input = event.target_unchecked_into::<HtmlInputElement>();
+    if let Some(country) = fuzzy_country_matches(&input.value()).next() {
+        input.set_value(&country.country_name());
+        return Msg::SetCitizenship(country.country_name());
+    }
+
+    Msg::NoOp
+}
+
+fn fuzzy_country_matches(query: &str) -> Box<dyn Iterator<Item = rust_iso3166::CountryCode> + '_> {
+    if query.is_empty() {
+        let rng = &mut thread_rng();
+        return Box::new(
+            rust_iso3166::ALL
+                .iter()
+                .choose_multiple(rng, 5)
+                .into_iter()
+                .map(Clone::clone),
+        );
+    }
+
+    let matcher = SkimMatcherV2::default();
+    let query = query.to_lowercase();
+    let query_len = query.len();
+
+    let mut results: Vec<_> = rust_iso3166::ALL
+        .iter()
+        .filter_map(|code| {
+            let mut score = None;
+            // Prioritize exact matches for country codes if query length <= 3
+            if query_len <= 3 {
+                if code.country_alpha2().eq_ignore_ascii_case(&query)
+                    || code.country_alpha3().eq_ignore_ascii_case(&query)
+                {
+                    score = Some(100);
+                }
+            }
+            // If no exact match, do a fuzzy match
+            if score.is_none() {
+                score = matcher.fuzzy_match(&code.country_name().to_lowercase(), &query);
+            }
+
+            score.map(|score| (score, code))
+        })
+        .collect();
+
+    // Sort by score in descending order
+    results.sort_by(|a, b| b.0.cmp(&a.0));
+
+    // Get iterator over the best matches
+    Box::new(results.into_iter().map(|(_score, code)| *code))
+}
+
+// Callback for an input element's value being mapped straight into a
+// message.
+fn input_message(e: InputEvent, msg: fn(String) -> Msg) -> Msg {
+    let input = e.target_unchecked_into::<HtmlInputElement>();
+    msg(input.value())
+}
+
+// Callback for a text area's value being mapped straight into a
+// message.
+fn textarea_message(e: InputEvent, msg: fn(String) -> Msg) -> Msg {
+    let textarea = e.target_unchecked_into::<HtmlTextAreaElement>();
+    msg(textarea.value())
+}
+
+fn schedule_blur(event: FocusEvent, link: Scope<App>) -> Msg {
+    let input = event.target_unchecked_into::<HtmlInputElement>();
+    let closure = Closure::once_into_js(Box::new(move || {
+        if let Some(app) = link.get_component() {
+            input.set_value(&app.record.citizenship);
+        }
+
+        link.send_message(Msg::BlurCitizenship);
+    }) as Box<dyn FnOnce()>);
+
+    let window = web_sys::window().expect("no global `window` exists");
+    let _ =
+        window.set_timeout_with_callback_and_timeout_and_arguments_0(closure.unchecked_ref(), 100);
+
+    Msg::NoOp
+}
+
+/// Creates an input field for citizenship selection with suggestions.
+fn citizenship_input(app: &App, link: &Scope<App>) -> Html {
+    let dropdown_classes = if app.citizenship_focus {
+        "dropdown-menu show"
+    } else {
+        "dropdown-menu"
+    };
+
+    let choices = fuzzy_country_matches(&app.citizenship_query).map(|country| {
+        let msg = Msg::SetCitizenship(country.country_name());
+        html! {
+            <li><a class="dropdown-item" onclick={link.callback(move |_| msg.clone())}>{country.country_name()}</a></li>
+        }
+    });
+
+    let blur_link = link.clone();
+    html! {
+      <div class="dropdown">
+        <input type="text" class="form-control" id="citizenship" aria-describedby="citizenshipHelp"
+            autocomplete="off"
+            oninput={link.callback(|event| input_message(event, Msg::QueryCitizenship))}
+            onkeypress={link.callback(select_country_enter)}
+            onfocus={link.callback(|_| Msg::FocusCitizenship)}
+            onblur={link.callback(move |event| schedule_blur(event, blur_link.clone()))} />
+            <ul class={dropdown_classes} style="position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate(0px, 40px);" data-popper-placement="bottom-start">
+          { choices.collect::<Html>() }
+        </ul>
+      </div>
+    }
+}
+
+/// Creates a list of technologies which can be deleted again by clicking them.
+fn render_technologies(link: &Scope<App>, technologies: &BTreeSet<String>) -> Html {
+    if technologies.is_empty() {
+        return html! {};
+    }
+
+    let items = technologies.iter().map(|tech| {
+        let msg: Msg = Msg::RemoveTechnology(tech.to_string());
+        html! {
+            <>
+              <span class="btn btn-secondary btn-sm"
+                onclick={link.callback(move |_| msg.clone())}>
+                {tech}
+                <span class="mx-auto text-center text-black">{" x"}</span>
+              </span>{" "}
+            </>
+        }
+    });
+    html! {
+        <div class="p-1">
+            { items.collect::<Html>() }
+        </div>
+    }
+}
+
+impl Component for App {
+    type Message = Msg;
+    type Properties = ();
+
+    fn create(_ctx: &Context<Self>) -> Self {
+        let mut new = Self::default();
+
+        if let Ok(record) = LocalStorage::get("record") {
+            new.record = record;
+        }
+
+        new
+    }
+
+    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
+        console::log!("handling ", format!("{:?}", msg));
+        let (state_change, view_change) = match msg {
+            Msg::NoOp => (false, false),
+
+            Msg::AddTechnology(tech) => {
+                console::log!("adding technology", &tech);
+                self.record.technologies.insert(tech);
+                (true, true)
+            }
+
+            Msg::RemoveTechnology(tech) => {
+                console::log!("removing technology ", &tech);
+                self.record.technologies.remove(&tech);
+                (true, true)
+            }
+
+            Msg::QueryCitizenship(query) => {
+                self.citizenship_query = query;
+                (false, true)
+            }
+
+            Msg::FocusCitizenship => {
+                self.citizenship_focus = true;
+                (false, true)
+            }
+
+            Msg::BlurCitizenship => {
+                self.citizenship_focus = false;
+                (false, true)
+            }
+
+            Msg::SetCitizenship(country) => {
+                self.record.citizenship = country;
+                (true, false)
+            }
+
+            Msg::SetName(name) => {
+                self.record.name = name;
+                (true, false)
+            }
+
+            Msg::SetEmail(email) => {
+                self.record.email = email;
+                (true, false)
+            }
+
+            Msg::SetPersonalDetails(details) => {
+                self.record.personal_details = details;
+                (true, false)
+            }
+
+            Msg::SetPosition(position) => {
+                self.record.position = position;
+                (true, false)
+            }
+
+            Msg::SetJobDetails(details) => {
+                self.record.job_details = details;
+                (true, false)
+            }
+
+            Msg::SetWorkBackground(background) => {
+                self.record.work_background = background;
+                (true, false)
+            }
+        };
+
+        if state_change {
+            if let Err(err) = LocalStorage::set("record", &self.record) {
+                console::warn!(
+                    "failed to persist record in local storage: ",
+                    err.to_string()
+                );
+            }
+        }
+
+        view_change
+    }
+
+    fn view(&self, ctx: &Context<Self>) -> Html {
+        let link = ctx.link();
+
+        include!("home.html")
+    }
+}
+
+fn main() {
+    yew::Renderer::<App>::new().render();
+}