diff --git a/.gitignore b/.gitignore index 28cf9d022cd6..4186fc56a272 100644 --- a/.gitignore +++ b/.gitignore @@ -19,10 +19,18 @@ ################################################################################ ## Halide-specific exclusions -# Images only allowed in apps and directories named "images" +# Images only allowed in apps, directories named "images", and Halidoscope's +# icon set *.png !apps/**/*.png !**/images/**/*.png +!tools/halidoscope/src-tauri/icons/*.png + +# Halidoscope is desktop-only; `tauri icon` also emits mobile (Android/iOS) +# assets we don't want to commit. +tools/halidoscope/src-tauri/icons/android/ +tools/halidoscope/src-tauri/icons/ios/ + # Pre-trained weights only allowed in autoscheduler directories *.weights diff --git a/python_bindings/src/halide/halide_/PyPipeline.cpp b/python_bindings/src/halide/halide_/PyPipeline.cpp index f9544c2f9f62..642610e2b50e 100644 --- a/python_bindings/src/halide/halide_/PyPipeline.cpp +++ b/python_bindings/src/halide/halide_/PyPipeline.cpp @@ -64,6 +64,17 @@ void define_pipeline(py::module &m) { return ""; }); + py::class_(m, "HalidoscopeOptions") + .def(py::init<>()) + .def_readwrite("halidoscope_path", &HalidoscopeOptions::halidoscope_path) + .def_readwrite("halidoscope_output_dir", &HalidoscopeOptions::halidoscope_output_dir) + .def_readwrite("halidoscope_profile_runs", &HalidoscopeOptions::halidoscope_profile_runs) + .def("__repr__", [](const HalidoscopeOptions &o) -> std::string { + return ""; + }); + auto pipeline_class = py::class_(m, "Pipeline") .def(py::init<>()) @@ -227,6 +238,35 @@ void define_pipeline(py::module &m) { }, py::arg("dst"), py::arg("target") = Target()) + // Development/debugging aid: see Pipeline::halidoscope() in Pipeline.h. + // Blocks until the Halidoscope window is closed, so the GIL must be + // released for the duration of the call, same as realize() above. + .def("halidoscope", // + [](Pipeline &p, Buffer<> buffer, const HalidoscopeOptions &options, const Target &target) -> void { + py::gil_scoped_release release; + p.halidoscope(Realization(std::move(buffer)), options, target); // + }, + py::arg("dst"), py::arg("options") = HalidoscopeOptions(), py::arg("target") = Target()) + + // See the comment on the corresponding realize() overload above: this + // overload must be declared before the list-of-sizes one, so that an + // empty list [] is resolved as list-of-sizes (a 0-dimensional Buffer) + // rather than as an ambiguous empty list-of-buffers. + .def("halidoscope", // + [](Pipeline &p, std::vector sizes, const HalidoscopeOptions &options, const Target &target) -> void { + py::gil_scoped_release release; + p.halidoscope(std::move(sizes), options, target); // + }, + py::arg("sizes") = std::vector{}, py::arg("options") = HalidoscopeOptions(), py::arg("target") = Target()) + + // This will actually allow a list-of-buffers as well as a tuple-of-buffers, but that's OK. + .def("halidoscope", // + [](Pipeline &p, std::vector> buffers, const HalidoscopeOptions &options, const Target &target) -> void { + py::gil_scoped_release release; + p.halidoscope(Realization(std::move(buffers)), options, target); // + }, + py::arg("dst"), py::arg("options") = HalidoscopeOptions(), py::arg("target") = Target()) + .def("infer_input_bounds", // [](Pipeline &p, const py::object &dst, const Target &target) -> void { const Target t = to_jit_target(target); diff --git a/src/Pipeline.cpp b/src/Pipeline.cpp index e935fd4852df..64761e3d6714 100644 --- a/src/Pipeline.cpp +++ b/src/Pipeline.cpp @@ -1,4 +1,11 @@ #include +#include +#include +#include +#include +#include +#include +#include #include #include "Argument.h" @@ -16,6 +23,7 @@ #include "PrintLoopNest.h" #include "RealizationOrder.h" #include "Serialization.h" +#include "Util.h" #include "WasmExecutor.h" using namespace Halide::Internal; @@ -151,6 +159,8 @@ struct PipelineContents { bool trace_pipeline = false; + bool defer_profile_flush = false; + /** Optional prefixes used to rename halide_-prefixed runtime symbols. * Empty unless set via Pipeline::apply_runtime_prefixes(). */ RuntimePrefixParams runtime_prefixes_params; @@ -837,7 +847,11 @@ Realization Pipeline::realize(JITUserContext *context, } // If we're profiling, report runtimes and reset profiler stats. - contents->jit_cache.finish_profiling(context); + // Condition based on whether or not calling code has chosen to defer + // flushing profile information (e.g., to accumulate results across runs). + if (!contents->defer_profile_flush) { + contents->jit_cache.finish_profiling(context); + } jit_context.finalize(exit_status); // Crop back to the requested size if necessary @@ -900,6 +914,321 @@ void Pipeline::trace_pipeline() { contents->trace_pipeline = true; } +void Pipeline::set_defer_profile_flush(bool defer) { + user_assert(defined()) << "Pipeline is undefined\n"; + contents->defer_profile_flush = defer; +} + +void Pipeline::flush_profiler_state(JITUserContext *context) { + user_assert(defined()) << "Pipeline is undefined\n"; + JITUserContext empty{}; + contents->jit_cache.finish_profiling(context ? context : &empty); +} + +namespace { + +// State for the halidoscope_capture_trace callback below. custom_trace is a +// plain C function pointer (see JITHandlers in JITModule.h), so it can't +// capture anything -- this mirrors the approach taken by +// test/performance/profiler.cpp. +// +// halidoscope_trace_stream is not thread-safe to mutate, but +// Pipeline::halidoscope() is documented as non-reentrant (i.e. not to be called +// concurrently with itself), so that's fine; the trace writer below does still +// need to support *this* pipeline's own worker threads emitting trace events +// concurrently during a single run. + +std::ofstream *halidoscope_trace_stream = nullptr; +std::mutex halidoscope_trace_mutex; +std::atomic halidoscope_trace_next_id{1}; + +// Writes the same on-disk packet format as halide_default_trace's +// HL_TRACE_FILE path (src/runtime/tracing.cpp), but owns the output stream +// directly in host code instead of going through the runtime's env-var +// triggered, process-global file handle. That global (halide_trace_file) is +// cached per JIT-shared-runtime instance and halide_shutdown_trace() only +// ever resets it to a permanently-disabled state (0, not -1), so it cannot +// be safely reopened for a second trace within the same process -- writing +// packets ourselves sidesteps that entirely and works for any number of +// halidoscope() calls in one process. +int32_t halidoscope_capture_trace(JITUserContext *, const halide_trace_event_t *e) { + // The return value becomes the parent_id of any trace events nested + // inside this one (see halide_default_trace's use of `my_id`), so this + // must be computed regardless of whether we're actually writing it out. + int32_t my_id = halidoscope_trace_next_id.fetch_add(1); + if (!halidoscope_trace_stream) { + return my_id; + } + + const bool is_load_or_store = (e->event == halide_trace_load || e->event == halide_trace_store); + uint32_t value_bytes = is_load_or_store ? (uint32_t)(e->lanes * e->type.bytes()) : 0; + uint32_t header_bytes = (uint32_t)sizeof(halide_trace_packet_t); + uint32_t coords_bytes = (uint32_t)e->dimensions * (uint32_t)sizeof(int32_t); + uint32_t name_bytes = (uint32_t)strlen(e->func) + 1; + uint32_t trace_tag_bytes = e->trace_tag ? (uint32_t)strlen(e->trace_tag) + 1 : 1; + uint32_t total_size_without_padding = header_bytes + value_bytes + coords_bytes + name_bytes + trace_tag_bytes; + uint32_t total_size = (total_size_without_padding + 3) & ~3u; + + std::vector buf(total_size, 0); + auto *packet = reinterpret_cast(buf.data()); + packet->size = total_size; + packet->event = e->event; + packet->parent_id = e->parent_id; + packet->dimensions = e->dimensions; + if (is_load_or_store) { + packet->value_index = e->value_index; + packet->type_code = (uint8_t)e->type.code; + packet->type_bits = (uint8_t)e->type.bits; + packet->lanes = (uint16_t)e->lanes; + } else { + packet->id = my_id; + packet->thread_id = (e->event == halide_trace_begin_parallel_task) ? e->thread_id : 0; + } + if (e->coordinates) { + memcpy(packet->coordinates(), e->coordinates, coords_bytes); + } + if (e->value) { + memcpy(packet->value(), e->value, value_bytes); + } + memcpy(packet->func(), e->func, name_bytes); + memcpy(packet->trace_tag(), e->trace_tag ? e->trace_tag : "", trace_tag_bytes); + + { + std::scoped_lock lock(halidoscope_trace_mutex); + halidoscope_trace_stream->write(reinterpret_cast(buf.data()), total_size); + } + + return my_id; +} + +void halidoscope_append_func_stats(std::ostringstream &out, + const halide_profiler_func_stats &stats) { + out << "{" + << "\"name\":\"" << stats.name << "\"," + << "\"parent\":" << stats.parent << "," + << "\"canonical_id\":" << stats.canonical_id << "," + << "\"kind\":" << (int)stats.kind << "," + << "\"buffer_func_id\":" << stats.buffer_func_id << "," + << "\"time_ns\":" << stats.time << "," + << "\"memory_current\":" << stats.memory_current << "," + << "\"memory_peak\":" << stats.memory_peak << "," + << "\"memory_total\":" << stats.memory_total << "," + << "\"stack_peak\":" << stats.stack_peak << "," + << "\"active_threads_numerator\":" << stats.active_threads_numerator << "," + << "\"active_threads_denominator\":" << stats.active_threads_denominator << "," + << "\"num_allocs\":" << stats.num_allocs + << "}"; +} + +// Snapshots the profiler's pipeline-level stats into `profile_json`. Must be +// called after the profiling loop has finished (i.e. every profiled +// realize() call has returned) but before the stats are reset -- each +// realize() call's halide_profiler_instance_end runs (and merges that run's +// counters into halide_profiler_pipeline_stats) before the call returns to +// this C++ code, so by the time the loop finishes, the pipeline stats +// reflect all of the runs merged together, with per-Func times correctly +// averaged across billed_runs. (Pipeline::flush_profiler_state(), called via +// DeferredProfileFlush's destructor once this scope ends, is what resets +// them, so this needs to run before that.) +void halidoscope_write_profile_json(const Target &target, std::string &profile_json) { + using GetStateFn = halide_profiler_state *(*)(); + auto get_state = (GetStateFn)JITSharedRuntime::find_symbol(target, "halide_profiler_get_state"); + if (!get_state) { + return; + } + + // halidoscope() only ever has one instrumented pipeline running at a + // time, so the head of the pipeline list is always ours. + halide_profiler_pipeline_stats *ps = get_state()->pipelines; + if (!ps) { + return; + } + + std::ostringstream out; + out << "{\"pipelines\":[{" + << "\"name\":\"" << ps->name << "\"," + << "\"runs\":" << ps->runs << "," + << "\"billed_runs\":" << ps->billed_runs << "," + << "\"samples\":" << ps->samples << "," + << "\"num_allocs\":" << ps->num_allocs << "," + << "\"time_ns\":" << ps->time << "," + << "\"memory_current\":" << ps->memory_current << "," + << "\"memory_peak\":" << ps->memory_peak << "," + << "\"memory_total\":" << ps->memory_total << "," + << "\"active_threads_numerator\":" << ps->active_threads_numerator << "," + << "\"active_threads_denominator\":" << ps->active_threads_denominator << "," + << "\"funcs\":["; + for (int i = 0; i < ps->num_funcs; i++) { + if (i > 0) { + out << ","; + } + halidoscope_append_func_stats(out, ps->funcs[i]); + } + out << "]}]}"; + + profile_json = out.str(); +} + +// Builds a fresh, independent view over the same underlying output storage +// as `from`. RealizationArg is move-only and single-use by convention (its +// public realize() overload consumes it by value), but halidoscope() needs +// to realize into the same output twice (once per instrumented run; the +// results are discarded either way, so writing into the same storage twice +// is harmless). We can't just move `from` twice, so instead we copy out its +// (public) fields by hand. +Pipeline::RealizationArg halidoscope_clone_output(const Pipeline::RealizationArg &from) { + Pipeline::RealizationArg view(static_cast(nullptr)); + if (from.r) { + view.r = from.r; + } else if (from.buf) { + view.buf = from.buf; + } else if (from.buffer_list) { + view.buffer_list = std::make_unique>>(*from.buffer_list); + } + return view; +} + +} // namespace + +void Pipeline::halidoscope_impl(const std::function &do_realize, + const HalidoscopeOptions &options, + const Target &target_arg) { + user_assert(defined()) << "Pipeline is undefined\n"; + + std::string halidoscope_path = options.halidoscope_path.value_or("halidoscope"); + int profile_runs = options.halidoscope_profile_runs.value_or(1); + + user_assert(profile_runs >= 0) + << "halidoscope: HalidoscopeOptions::halidoscope_profile_runs must be a non-negative integer, got " + << profile_runs << ".\n"; + + // Fail fast if halidoscope_path looks like an explicit path (as opposed + // to a bare name meant to be resolved via $PATH, e.g. the default + // "halidoscope") and nothing exists there -- no need to burn two full + // pipeline realizations before discovering the binary is missing. A bare + // name can't be checked this way (file_exists() just wraps access(), + // which won't search $PATH), so that case is instead caught below, after + // actually trying to launch it. + if (halidoscope_path.find('/') != std::string::npos) { + user_assert(file_exists(halidoscope_path)) + << "halidoscope: no file found at HalidoscopeOptions::halidoscope_path='" + << halidoscope_path << "'.\n"; + } + + // Pipeline::compile_jit() discards the *entire* target (feature bits + // included) and replaces it with get_jit_target_from_environment() + // whenever has_unknowns() is true -- so an unresolved target_arg (e.g. + // the default Target()) would silently lose the trace/profile features + // we're about to add. Resolve it first so those features actually reach + // lowering/codegen. + Target base_target = target_arg.has_unknowns() ? get_jit_target_from_environment() : target_arg; + + std::map external_params; + std::vector data; + serialize_pipeline(*this, data, external_params); + + std::string dir = options.halidoscope_output_dir ? *options.halidoscope_output_dir : dir_make_temp(); + std::string trace_path = dir + "/trace.hltrace"; + std::string profile_path = dir + "/profile.json"; + + // --- Trace run: every Func's loads/stores/realizations, dumped to a + // binary trace file in the same format Halide's HL_TRACE_FILE path + // writes (see halidoscope_capture_trace above for why we write it + // ourselves instead of using HL_TRACE_FILE directly). --- + { + Pipeline traced = deserialize_pipeline(data, external_params); + traced.trace_pipeline(); + Target trace_target = base_target + .with_feature(Target::TraceLoads) + .with_feature(Target::TraceStores) + .with_feature(Target::TraceRealizations); + + std::ofstream trace_stream(trace_path, std::ios::binary); + user_assert(trace_stream.good()) << "halidoscope: unable to open " << trace_path << " for writing\n"; + halidoscope_trace_next_id = 1; + halidoscope_trace_stream = &trace_stream; + traced.jit_handlers().custom_trace = halidoscope_capture_trace; + + do_realize(traced, trace_target); + + halidoscope_trace_stream = nullptr; + trace_stream.close(); + } + + // --- Profile run: Halide's sampling profiler, captured into JSON. --- + struct DeferredProfileFlush { + Pipeline &p; + explicit DeferredProfileFlush(Pipeline &pipeline) : p(pipeline) { + p.set_defer_profile_flush(true); + } + ~DeferredProfileFlush() { + p.set_defer_profile_flush(false); + p.flush_profiler_state(); + } + }; + + if (profile_runs > 0) { + Pipeline profiled = deserialize_pipeline(data, external_params); + Target profile_target = base_target.with_feature(Target::Profile); + + std::string profile_json; + + { + DeferredProfileFlush guard(profiled); + for (int i = 0; i < profile_runs; i++) { + std::cout << "Halidoscope profiling run " << i + 1 << " of " << profile_runs << "\n"; + do_realize(profiled, profile_target); + } + // Snapshot the merged stats now, while the guard is still + // deferring the flush that would otherwise reset them. + halidoscope_write_profile_json(profile_target, profile_json); + } + + write_entire_file(profile_path, profile_json.data(), profile_json.size()); + } + + // --- Launch Halidoscope, blocking until the window is closed. --- + std::string binary = halidoscope_path; + + std::vector halidoscope_args = {binary, "--trace", trace_path}; + if (profile_runs > 0) { + halidoscope_args.emplace_back("--profile"); + halidoscope_args.emplace_back(profile_path); + } + + int halidoscope_rc = run_process(halidoscope_args); + + // If we did not specify a persistent output directory, clean up the + // temporary directory storing trace and profile data. + if (!options.halidoscope_output_dir) { + file_unlink(trace_path); + if (profile_runs > 0) { + file_unlink(profile_path); + } + dir_rmdir(dir); + } + + // run_process() returns -1 if the binary couldn't be started at all (as + // opposed to running and exiting with a nonzero status) -- for a bare + // name like the default "halidoscope", that almost always means it + // wasn't found on $PATH (an explicit path is already checked above). + user_assert(halidoscope_rc != -1) + << "halidoscope: could not find or launch the Halidoscope binary '" << binary + << "'. Make sure it is installed and on $PATH, or set " + "HalidoscopeOptions::halidoscope_path to point at it directly.\n"; +} + +void Pipeline::halidoscope(std::vector sizes, const HalidoscopeOptions &options, const Target &target) { + halidoscope_impl([&sizes](Pipeline &p, const Target &t) { p.realize(sizes, t); }, options, target); +} + +void Pipeline::halidoscope(RealizationArg output, const HalidoscopeOptions &options, const Target &target) { + halidoscope_impl([&output](Pipeline &p, const Target &t) { + p.realize(halidoscope_clone_output(output), t); + }, + options, target); +} + // Make a vector of void *'s to pass to the jit call using the // currently bound value for all of the params and image // params. @@ -1114,7 +1443,12 @@ void Pipeline::realize(JITUserContext *context, debug(2) << "Back from jitted function. Exit status was " << exit_status << "\n"; // If we're profiling, report runtimes and reset profiler stats. - contents->jit_cache.finish_profiling(context); + // Condition based on whether or not calling code has chosen to defer + // flushing profile information (e.g., to accumulate results across + // many runs). + if (!contents->defer_profile_flush) { + contents->jit_cache.finish_profiling(context); + } jit_call_context.finalize(exit_status); } diff --git a/src/Pipeline.h b/src/Pipeline.h index 0fa8b593eedf..4e0c8a538293 100644 --- a/src/Pipeline.h +++ b/src/Pipeline.h @@ -114,6 +114,22 @@ struct AutoSchedulerResults { std::vector featurization; // The featurization of the pipeline (if any) }; +/** Options controlling how Pipeline::halidoscope() locates and launches the + * Halidoscope GUI binary. */ +struct HalidoscopeOptions { + /** (Optional) Path to the halidoscope executable, or just its name if + * it's on $PATH. If unset, defaults to looking up "halidoscope" on + * $PATH. */ + std::optional halidoscope_path = std::nullopt; + /** (Optional) Path to the non-volatile directory for storing + * Halidoscope-generated trace binaries and profiler output. */ + std::optional halidoscope_output_dir = std::nullopt; + /** (Optional) The number of runs for the profiler execution. If unset, + * defaults to 1. A 0 value indicates that profiling should be + * skipped. */ + std::optional halidoscope_profile_runs = std::nullopt; +}; + class Pipeline; using AutoSchedulerFn = std::function; @@ -514,8 +530,44 @@ class Pipeline { /** Generate begin_pipeline and end_pipeline tracing calls for this pipeline. */ void trace_pipeline(); + /** Development/debugging aid: run this pipeline twice under + * instrumentation (once with full tracing enabled, once with the + * profiler enabled), write the resulting trace and profile artifacts to + * a temporary directory, and open them in the Halidoscope GUI + * (https://github.com/halide/Halide, tools/halidoscope). The + * `halidoscope` executable is looked up on $PATH by default; pass a + * HalidoscopeOptions with halidoscope_path set to override + * that. + * + * This performs two additional realizations of the pipeline purely for + * the sake of instrumentation -- it does not realize the "real" output + * for the caller, and any output produced by these runs is discarded. + * This method blocks until the Halidoscope window is closed, at which + * point the temporary directory is deleted. + * + * Not reentrant/thread-safe; do not call this concurrently with itself + * or with another Halide JIT realization in the same process. */ + // @{ + void halidoscope(std::vector sizes, const HalidoscopeOptions &options = HalidoscopeOptions(), const Target &target = Target()); + void halidoscope(RealizationArg output, const HalidoscopeOptions &options = HalidoscopeOptions(), const Target &target = Target()); + // @} + + /** Set a flag to defer automatically flushing profiler state when calling + * Pipeline::realize, which automatically calls jit_cache.finish_profiling() + * after each realization in a JIT pipeline. Useful to accumulate metrics + * from multiple profiling runs. */ + void set_defer_profile_flush(bool defer); + + /** Immediately flush profiler state, using the profiler's built-in state + * accumulation logic. */ + void flush_profiler_state(JITUserContext *context = nullptr); + private: std::string generate_function_name() const; + + void halidoscope_impl(const std::function &do_realize, + const HalidoscopeOptions &options, + const Target &target_arg); }; struct ExternSignature { diff --git a/test/correctness/CMakeLists.txt b/test/correctness/CMakeLists.txt index 5ca439110784..a093d85dfd90 100644 --- a/test/correctness/CMakeLists.txt +++ b/test/correctness/CMakeLists.txt @@ -188,6 +188,7 @@ tests( growing_stack.cpp half_native_interleave.cpp halide_buffer.cpp + halidoscope.cpp handle.cpp heap_cleanup.cpp hello_gpu.cpp @@ -566,4 +567,5 @@ set_target_properties( if (WITH_SERIALIZATION) target_compile_definitions(correctness_streaming PRIVATE TEST_WITH_SERIALIZATION) target_compile_definitions(correctness_generator_cache PRIVATE TEST_WITH_SERIALIZATION) + target_compile_definitions(correctness_halidoscope PRIVATE TEST_WITH_SERIALIZATION) endif () diff --git a/test/correctness/halidoscope.cpp b/test/correctness/halidoscope.cpp new file mode 100644 index 000000000000..bf9e542ce160 --- /dev/null +++ b/test/correctness/halidoscope.cpp @@ -0,0 +1,192 @@ +// Exercises Pipeline::halidoscope(). Each check runs the real trace and +// profile instrumentation, but avoids depending on the actual Halidoscope +// GUI binary being built or installed anywhere in this environment by +// pointing HalidoscopeOptions::halidoscope_path at this test binary itself, +// re-exec'd with the same "--trace [--profile ]" argv shape +// halidoscope_impl() would pass to the real thing -- mirroring +// test/correctness/run_process.cpp. +// +// halidoscope() serializes and deserializes the pipeline internally, so it's +// only meaningful when Halide was built with serialization support; +// TEST_WITH_SERIALIZATION is defined by CMake in that case. + +#include "Halide.h" + +#include +#include +#include +#include +#include +#include + +using namespace Halide; + +#ifdef TEST_WITH_SERIALIZATION + +namespace { + +namespace fs = std::filesystem; + +// Return 1 from main() on failure; the test harness treats that as a failure. +// (assert() is compiled out in release builds, so we can't rely on it.) +#define check(cond) \ + do { \ + if (!(cond)) { \ + std::cerr << "FAILED: " #cond " (line " << __LINE__ << ")\n"; \ + return 1; \ + } \ + } while (0) + +std::vector slurp(const std::string &path) { + return Internal::read_entire_file(path); +} + +bool contains(const std::vector &haystack, const std::string &needle) { + return std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end()) != haystack.end(); +} + +// A minimal 2-stage pipeline: enough for halidoscope()'s tracing and +// profiling to have more than one Func to report on. +Pipeline make_test_pipeline(Func &f, Func &g) { + Var x("x"), y("y"); + f(x, y) = x + y; + g(x, y) = f(x, y) * 2; + f.compute_root(); + return Pipeline(g); +} + +bool exception_thrown(const std::function &fn) { + try { + fn(); + } catch (const Error &) { + return true; + } + return false; +} + +} // namespace + +int main(int argc, char **argv) { + // Stub-launcher mode: stand in for the real Halidoscope GUI binary. + // halidoscope_impl() invokes its launcher with exactly this argv shape, + // so re-exec'ing this test binary lets the checks below exercise a real + // launch without needing the actual GUI app. + if (argc >= 3 && std::string(argv[1]) == "--trace") { + return 0; + } + + const std::string self = fs::absolute(argv[0]).string(); + + // Happy path: one traced realization and one profiled realization + // should be written to halidoscope_output_dir, and the "launch" (our + // stub) should succeed without halidoscope() throwing. + { + Func f("f"), g("g"); + Pipeline p = make_test_pipeline(f, g); + + std::string dir = Internal::dir_make_temp(); + HalidoscopeOptions options; + options.halidoscope_path = self; + options.halidoscope_output_dir = dir; + + p.halidoscope({16, 16}, options); + + std::string trace_path = dir + "/trace.hltrace"; + std::string profile_path = dir + "/profile.json"; + check(Internal::file_exists(trace_path)); + check(Internal::file_exists(profile_path)); + + // Every load/store/realization event the tracing hook writes is at + // least the size of one halide_trace_packet_t; a realization of two + // Funcs over a 16x16 domain produces many such events, so a + // near-empty file would indicate tracing silently didn't run. + check(slurp(trace_path).size() > 1024); + + std::vector profile_json = slurp(profile_path); + check(contains(profile_json, "\"pipelines\":[")); + check(!contains(profile_json, "\"funcs\":[]")); + + Internal::file_unlink(trace_path); + Internal::file_unlink(profile_path); + Internal::dir_rmdir(dir); + } + + // halidoscope_profile_runs == 0 should skip the profiling run entirely + // (no profile.json), while still writing the trace as usual. + { + Func f("f"), g("g"); + Pipeline p = make_test_pipeline(f, g); + + std::string dir = Internal::dir_make_temp(); + HalidoscopeOptions options; + options.halidoscope_path = self; + options.halidoscope_output_dir = dir; + options.halidoscope_profile_runs = 0; + + p.halidoscope({16, 16}, options); + + check(Internal::file_exists(dir + "/trace.hltrace")); + check(!Internal::file_exists(dir + "/profile.json")); + + Internal::file_unlink(dir + "/trace.hltrace"); + Internal::dir_rmdir(dir); + } + + // The Buffer-realization overload should behave the same as the sizes + // overload. + { + Func f("f"), g("g"); + Pipeline p = make_test_pipeline(f, g); + + std::string dir = Internal::dir_make_temp(); + HalidoscopeOptions options; + options.halidoscope_path = self; + options.halidoscope_output_dir = dir; + + Buffer out(16, 16); + p.halidoscope(out, options); + + check(Internal::file_exists(dir + "/trace.hltrace")); + check(Internal::file_exists(dir + "/profile.json")); + + Internal::file_unlink(dir + "/trace.hltrace"); + Internal::file_unlink(dir + "/profile.json"); + Internal::dir_rmdir(dir); + } + + // An explicit halidoscope_path that doesn't exist should fail fast, + // before any instrumentation or launch is attempted. + { + Func f("f"), g("g"); + Pipeline p = make_test_pipeline(f, g); + + HalidoscopeOptions options; + options.halidoscope_path = "/no/such/path/to/halidoscope"; + + check(exception_thrown([&]() { p.halidoscope({16, 16}, options); })); + } + + // A bare binary name that can't be found on $PATH should fail after + // trying (and failing) to launch it. + { + Func f("f"), g("g"); + Pipeline p = make_test_pipeline(f, g); + + HalidoscopeOptions options; + options.halidoscope_path = "not_halidoscope"; + + check(exception_thrown([&]() { p.halidoscope({16, 16}, options); })); + } + + std::cout << "Success!\n"; + return 0; +} + +#else // TEST_WITH_SERIALIZATION + +int main() { + std::cout << "[SKIP] halidoscope requires WITH_SERIALIZATION.\n"; + return 0; +} + +#endif // TEST_WITH_SERIALIZATION diff --git a/tools/halidoscope/.gitignore b/tools/halidoscope/.gitignore new file mode 100644 index 000000000000..fbdd7c491538 --- /dev/null +++ b/tools/halidoscope/.gitignore @@ -0,0 +1,25 @@ +/node_modules/ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/tools/halidoscope/.prettierignore b/tools/halidoscope/.prettierignore new file mode 100644 index 000000000000..42152d9a2864 --- /dev/null +++ b/tools/halidoscope/.prettierignore @@ -0,0 +1,5 @@ +node_modules +/src-tauri/* +!/src-tauri/tauri.conf.json +.vscode +*.yaml diff --git a/tools/halidoscope/.prettierrc b/tools/halidoscope/.prettierrc new file mode 100644 index 000000000000..394f7d50fce3 --- /dev/null +++ b/tools/halidoscope/.prettierrc @@ -0,0 +1 @@ +{ "plugins": ["prettier-plugin-tailwindcss"] } diff --git a/tools/halidoscope/README.md b/tools/halidoscope/README.md new file mode 100644 index 000000000000..b0f61dc0e2f4 --- /dev/null +++ b/tools/halidoscope/README.md @@ -0,0 +1,171 @@ +# Halidoscope + +An interactive GUI and CLI for working with Halide traces. + +## Prerequisites + +You'll need a few prerequisites to get everything working. + +1. Tauri's + [system dependencies](https://v2.tauri.app/start/prerequisites/#system-dependencies) + for your OS. + - Note that you only need dependencies for desktop targets. +2. A [Rust](https://rust-lang.org/learn/get-started/) installation. +3. A [Node.js](https://nodejs.org/en/download) installation. +4. [PNPM](https://pnpm.io/), a space-efficient package manager for the + JavaScript ecosystem. + +## Building Halidoscope + +To get a production build locally, run the following two commands: + +```bash +pnpm install +pnpm tauri build +``` + +This will write the Halidoscope executable to +`tools/halidoscope/src-tauri/target/release/halidoscope`. You can, of course, +symlink this executable to any directory on your `PATH`. On Unix systems: + +```bash +ln -sf tools/halidoscope/src-tauri/target/release/halidoscope /some/dir/on/your/path/halidoscope +``` + +## Using Halidoscope + +### Calling Halidoscope from a Halide program + +Halide exposes a member function on the `Pipeline` class, +`Pipeline::halidoscope`, that allows you to launch Halidoscope directly from an +executing program. This call will execute the your pipeline once with tracing +enabled, once with profiling enabled, and then launch an interactive Halidoscope +session. + +```cpp +// Normal algorithm definition and scheduling code. + +// Create the pipeline. +Pipeline pipeline(output); + +// Example call where we explicitly pass the size of output buffer we want +// Halidoscope to allocate. In this case, we want to match to the dimensions of +// our input buffer exactly. +std::vector sizes = {input.width(), input.height(), input.channels()}; +pipeline.halidoscope(sizes); +``` + +The first argument to `Pipeline::halidoscope` matches that of +`Pipeline::realize` and can be one of: + +1. A `sizes` `std::vector` defining the dimensionality of output + buffers for Halidoscope (and, under the hood, the Halide runtime) to + allocate. +2. An `output` `RealizationArg` representing an already-allocated destination + (e.g., a `Buffer`) for Halidoscope to write to. + +#### Configuring Halidoscope's behavior + +The `Pipeline::halidoscope` API also accepts an `options` argument of type +`HalidoscopeOptions` that can control how Halidoscope behaves. This struct has +the following shape: + +| Field | Type | Description | +| -------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `halidoscope_path` | `std::optional` | The path to the Halidoscope binary on disk. By default, `Pipeline::halidoscope` will look for `halidoscope` on the user's `$PATH` and error if not found. | +| `halidoscope_output_dir` | `std::optional` | (Optional.) A path to a non-volatile directory for storing Halidoscope-generated trace binaries and profiler output. By default, Halidoscope will write recorded `.hltrace` and profile JSON files to a temporary directory that is destroyed on process exit. | +| `halidoscope_profile_runs` | `std::optional` | The number of profiling runs for the Halide profiler to execute on the pipeline. Defaults to 1. Users can opt out of profiling altogether by specifying 0. | + +### Calling Halidoscope from the command line + +As an alternative to the `Pipeline::halidoscope` API, you can also invoke +Halidoscope directly from the command line to launch the GUI. To work with a +pre-recorded trace, simply specify the path to a Halide trace binary file via +the `--trace` flag. + +```bash +halidoscope --trace +``` + +If you'd also like to visualize a pre-recorded profile JSON file, pass the path +to that file via the `--profile` flag. Note that `--trace` is always required. + +```bash +halidoscope --trace --profile +``` + +### Additional CLI commands + +`halidoscope` also exposes a non-interactive CLI for gathering information about +your Halide pipeline. + +#### `list` + +List the `Func`s in a trace, along with their dimensionality. + +```bash +halidoscope list --trace [--json] +``` + +- `-t, --trace ` (required): Path to the `.hltrace` file to analyze. +- `--json`: Print output as JSON instead of a table. + +#### `stats` + +Print statistics (minimum/maximum coordinates, minimum/maximum value, maximum +store/load counts, and thread count) for one or all `Func`s in a trace. + +```bash +halidoscope stats --trace [--func ] [--json] +``` + +- `-t, --trace ` (required): Path to the `.hltrace` file to analyze. +- `-f, --func `: Name of the Func to print statistics for. If omitted, + prints statistics for all Funcs. +- `--json`: Print output as JSON instead of a table. + +#### `dot` + +Generate a [Graphviz DOT](https://graphviz.org/doc/info/lang.html) +representation of the pipeline's dataflow graph. + +```bash +halidoscope dot --trace [destination] +``` + +- `-t, --trace ` (required): Path to the `.hltrace` file to analyze. +- `destination` (optional): Path to write the DOT file. Must end in `.txt`, + `.gv`, or `.dot`. If omitted, prints the DOT source to stdout. + +#### `snapshot` + +Snapshot a `Func`'s values at a given packet index for a given render mode, +writing the underlying data to a JSON file. + +```bash +halidoscope snapshot --trace --func [--packet-index ] [--mode ] +``` + +- `-t, --trace ` (required): Path to the `.hltrace` file to snapshot. +- `-f, --func ` (required): Name of the Func to snapshot. +- `-i, --packet-index `: Global packet index to snapshot. Defaults to `0`. +- `-m, --mode `: Rendering mode. One of `grayscale` (default), `rgb` + `store-frequency`, `load-frequency`, `redundant-stores`, or `reuse-distance`. +- `destination` (required): Path to write the output snapshot. Must end in + `.json`. + +## Developing Halidoscope + +Developing Halidoscope locally should be fairly straightforward. Assuming you've +installed the [prequisities](#prerequisites), just run the following two +commands. + +```bash +pnpm install +pnpm tauri dev -- -- --trace [--profile ] +``` + +These commands will install all necessary JavaScript and Rust dependencies, +build the Rust backend (using the `dev` profile), and start Vite's dev server. +Changes on both the Rust and TypeScript sides will trigger automatic rebuilds +with hot reloading — no need to stop your dev server while developing! diff --git a/tools/halidoscope/eslint.config.mjs b/tools/halidoscope/eslint.config.mjs new file mode 100644 index 000000000000..01614fdde846 --- /dev/null +++ b/tools/halidoscope/eslint.config.mjs @@ -0,0 +1,24 @@ +// @ts-check + +import js from "@eslint/js"; +import { defineConfig, globalIgnores } from "eslint/config"; +import reactHooks from "eslint-plugin-react-hooks"; +import tseslint from "typescript-eslint"; + +export default defineConfig([ + globalIgnores(["src-tauri/**"]), + { + files: ["**/*.{js,ts,tsx}"], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + ], + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }, + ], + }, + }, +]); diff --git a/tools/halidoscope/index.html b/tools/halidoscope/index.html new file mode 100644 index 000000000000..ff93803bbc0a --- /dev/null +++ b/tools/halidoscope/index.html @@ -0,0 +1,14 @@ + + + + + + + Tauri + React + Typescript + + + +
+ + + diff --git a/tools/halidoscope/package.json b/tools/halidoscope/package.json new file mode 100644 index 000000000000..e39f90d415a4 --- /dev/null +++ b/tools/halidoscope/package.json @@ -0,0 +1,48 @@ +{ + "name": "halidoscope", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri", + "format": "prettier --write .", + "check:types": "tsc --noEmit", + "lint": "eslint src/**/*.{ts,tsx}" + }, + "dependencies": { + "@dagrejs/dagre": "^3.0.0", + "@observablehq/plot": "^0.6.17", + "@tailwindcss/vite": "^4.3.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-cli": "^2.4.1", + "@tauri-apps/plugin-dialog": "~2.7.2", + "@tauri-apps/plugin-opener": "^2", + "@xyflow/react": "^12.11.0", + "clsx": "^2.1.1", + "d3": "^7.9.0", + "jotai": "^2.20.1", + "motion": "^12.43.0", + "radix-ui": "^1.4.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@tauri-apps/cli": "^2.11.4", + "@types/d3": "^7.4.3", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^6.0.5", + "eslint": "^10.4.1", + "eslint-plugin-react-hooks": "^7.1.1", + "prettier": "^3.8.4", + "prettier-plugin-tailwindcss": "^0.8.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.60.1", + "vite": "^8.2.0" + } +} diff --git a/tools/halidoscope/pnpm-lock.yaml b/tools/halidoscope/pnpm-lock.yaml new file mode 100644 index 000000000000..528c4c8b8910 --- /dev/null +++ b/tools/halidoscope/pnpm-lock.yaml @@ -0,0 +1,4677 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@dagrejs/dagre': + specifier: ^3.0.0 + version: 3.0.0 + '@observablehq/plot': + specifier: ^0.6.17 + version: 0.6.17 + '@tailwindcss/vite': + specifier: ^4.3.0 + version: 4.3.0(vite@8.2.0(jiti@2.7.0)) + '@tauri-apps/api': + specifier: ^2 + version: 2.11.0 + '@tauri-apps/plugin-cli': + specifier: ^2.4.1 + version: 2.4.1 + '@tauri-apps/plugin-dialog': + specifier: ~2.7.2 + version: 2.7.2 + '@tauri-apps/plugin-opener': + specifier: ^2 + version: 2.5.4 + '@xyflow/react': + specifier: ^12.11.0 + version: 12.11.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + d3: + specifier: ^7.9.0 + version: 7.9.0 + jotai: + specifier: ^2.20.1 + version: 2.20.1(@babel/core@7.29.7)(@babel/template@7.29.7)(@types/react@19.2.15)(react@19.2.6) + motion: + specifier: ^12.43.0 + version: 12.43.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: + specifier: ^19.1.0 + version: 19.2.6 + react-dom: + specifier: ^19.1.0 + version: 19.2.6(react@19.2.6) + tailwindcss: + specifier: ^4.3.0 + version: 4.3.0 + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.4.1(jiti@2.7.0)) + '@tauri-apps/cli': + specifier: ^2.11.4 + version: 2.11.4 + '@types/d3': + specifier: ^7.4.3 + version: 7.4.3 + '@types/react': + specifier: ^19.1.8 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.2.3(@types/react@19.2.15) + '@vitejs/plugin-react': + specifier: ^6.0.5 + version: 6.0.5(vite@8.2.0(jiti@2.7.0)) + eslint: + specifier: ^10.4.1 + version: 10.4.1(jiti@2.7.0) + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.4.1(jiti@2.7.0)) + prettier: + specifier: ^3.8.4 + version: 3.8.4 + prettier-plugin-tailwindcss: + specifier: ^0.8.0 + version: 0.8.0(prettier@3.8.4) + typescript: + specifier: ~5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: ^8.60.1 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + vite: + specifier: ^8.2.0 + version: 8.2.0(jiti@2.7.0) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@dagrejs/dagre@3.0.0': + resolution: {integrity: sha512-ZzhnTy1rfuoew9Ez3EIw4L2znPGnYYhfn8vc9c4oB8iw6QAsszbiU0vRhlxWPFnmmNSFAkrYeF1PhM5m4lAN0Q==} + + '@dagrejs/graphlib@4.0.1': + resolution: {integrity: sha512-IvcV6FduIIAmLwnH+yun+QtV36SC7mERqa86aClNqmMN09WhmPPYU8ckHrZBozErf+UvHPWOTJYaGYiIcs0DgA==} + + '@emnapi/core@2.0.0-alpha.3': + resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} + + '@emnapi/runtime@2.0.0-alpha.3': + resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==} + + '@emnapi/wasi-threads@2.0.1': + resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.2.2': + resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + + '@observablehq/plot@0.6.17': + resolution: {integrity: sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==} + engines: {node: '>=12'} + + '@oxc-project/types@0.142.0': + resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/binding-android-arm64@1.2.1': + resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.1': + resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.1': + resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.1': + resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.1': + resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.1': + resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.1': + resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.1': + resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.1': + resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.1': + resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.1': + resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.1': + resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.2.1': + resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + + '@rolldown/binding-win32-arm64-msvc@1.2.1': + resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.1': + resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tauri-apps/api@2.11.0': + resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} + + '@tauri-apps/cli-darwin-arm64@2.11.4': + resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.11.4': + resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-x64-musl@2.11.4': + resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.11.4': + resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} + engines: {node: '>= 10'} + hasBin: true + + '@tauri-apps/plugin-cli@2.4.1': + resolution: {integrity: sha512-8JXofQFI5cmiGolh1PlU4hzE2YJgrgB1lyaztyBYiiMCy13luVxBXaXChYPeqMkUo46J1UadxvYdjRjj0E8zaw==} + + '@tauri-apps/plugin-dialog@2.7.2': + resolution: {integrity: sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==} + + '@tauri-apps/plugin-opener@2.5.4': + resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@typescript-eslint/eslint-plugin@8.60.1': + resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.1': + resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.1': + resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.1': + resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.1': + resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.1': + resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.1': + resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.1': + resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.1': + resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.1': + resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@6.0.5': + resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@xyflow/react@12.11.0': + resolution: {integrity: sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA==} + peerDependencies: + '@types/react': '>=17' + '@types/react-dom': '>=17' + react: '>=17' + react-dom: '>=17' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@xyflow/system@0.0.77': + resolution: {integrity: sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-search-bounds@2.0.5: + resolution: {integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + + enhanced-resolve@5.22.1: + resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} + engines: {node: '>=10.13.0'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + framer-motion@12.43.0: + resolution: {integrity: sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + interval-tree-1d@1.0.4: + resolution: {integrity: sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isoformat@0.2.1: + resolution: {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jotai@2.20.1: + resolution: {integrity: sha512-dnuKfU/GLi8B28RRMjQ3AfoN7kfzP8o41+AX2FmITZqEMY8PHnjABq+VkEooomLwYaGjda+pgy0yFSjaHX/ZPg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@babel/core': '>=7.0.0' + '@babel/template': '>=7.0.0' + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/template': + optional: true + '@types/react': + optional: true + react: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + motion-dom@12.43.0: + resolution: {integrity: sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==} + + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + + motion@12.43.0: + resolution: {integrity: sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.25: + resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-tailwindcss@0.8.0: + resolution: {integrity: sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + rolldown@1.2.1: + resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.60.1: + resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite@8.2.0: + resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@dagrejs/dagre@3.0.0': + dependencies: + '@dagrejs/graphlib': 4.0.1 + + '@dagrejs/graphlib@4.0.1': {} + + '@emnapi/core@2.0.0-alpha.3': + dependencies: + '@emnapi/wasi-threads': 2.0.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@2.0.0-alpha.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@2.0.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': + dependencies: + eslint: 10.4.1(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.4.1(jiti@2.7.0))': + optionalDependencies: + eslint: 10.4.1(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@floating-ui/utils@0.2.11': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': + dependencies: + '@emnapi/core': 2.0.0-alpha.3 + '@emnapi/runtime': 2.0.0-alpha.3 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@observablehq/plot@0.6.17': + dependencies: + d3: 7.9.0 + interval-tree-1d: 1.0.4 + isoformat: 0.2.1 + + '@oxc-project/types@0.142.0': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/binding-android-arm64@1.2.1': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.1': + optional: true + + '@rolldown/binding-darwin-x64@1.2.1': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.1': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.1': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.1': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.1': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.1': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.1': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.1': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.1': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.1': + optional: true + + '@rolldown/binding-wasm32-wasi@1.2.1': + dependencies: + '@emnapi/core': 2.0.0-alpha.3 + '@emnapi/runtime': 2.0.0-alpha.3 + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.1': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.1': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.22.1 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/vite@4.3.0(vite@8.2.0(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 8.2.0(jiti@2.7.0) + + '@tauri-apps/api@2.11.0': {} + + '@tauri-apps/cli-darwin-arm64@2.11.4': + optional: true + + '@tauri-apps/cli-darwin-x64@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + optional: true + + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.11.4': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + optional: true + + '@tauri-apps/cli@2.11.4': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.11.4 + '@tauri-apps/cli-darwin-x64': 2.11.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 + '@tauri-apps/cli-linux-arm64-musl': 2.11.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-musl': 2.11.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 + '@tauri-apps/cli-win32-x64-msvc': 2.11.4 + + '@tauri-apps/plugin-cli@2.4.1': + dependencies: + '@tauri-apps/api': 2.11.0 + + '@tauri-apps/plugin-dialog@2.7.2': + dependencies: + '@tauri-apps/api': 2.11.0 + + '@tauri-apps/plugin-opener@2.5.4': + dependencies: + '@tauri-apps/api': 2.11.0 + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/geojson@7946.0.16': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.1(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.8.3) + '@typescript-eslint/types': 8.60.1 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.60.1': {} + + '@typescript-eslint/typescript-estree@8.60.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.8.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@6.0.5(vite@8.2.0(jiti@2.7.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.0(jiti@2.7.0) + + '@xyflow/react@12.11.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@xyflow/system': 0.0.77 + classcat: 5.0.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + zustand: 4.5.7(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + transitivePeerDependencies: + - immer + + '@xyflow/system@0.0.77': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.33: {} + + binary-search-bounds@2.0.5: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + caniuse-lite@1.0.30001793: {} + + classcat@5.0.5: {} + + clsx@2.1.1: {} + + commander@7.2.0: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + electron-to-chromium@1.5.364: {} + + enhanced-resolve@5.22.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.1.1(eslint@10.4.1(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.4.1(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.1(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + framer-motion@12.43.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + motion-dom: 12.43.0 + motion-utils: 12.39.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + graceful-fs@4.2.11: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + internmap@2.0.3: {} + + interval-tree-1d@1.0.4: + dependencies: + binary-search-bounds: 2.0.5 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + isoformat@0.2.1: {} + + jiti@2.7.0: {} + + jotai@2.20.1(@babel/core@7.29.7)(@babel/template@7.29.7)(@types/react@19.2.15)(react@19.2.6): + optionalDependencies: + '@babel/core': 7.29.7 + '@babel/template': 7.29.7 + '@types/react': 19.2.15 + react: 19.2.6 + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + motion-dom@12.43.0: + dependencies: + motion-utils: 12.39.0 + + motion-utils@12.39.0: {} + + motion@12.43.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + framer-motion: 12.43.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + ms@2.1.3: {} + + nanoid@3.3.16: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.46: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + picomatch@4.0.5: {} + + postcss@8.5.25: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-tailwindcss@0.8.0(prettier@3.8.4): + dependencies: + prettier: 3.8.4 + + prettier@3.8.4: {} + + punycode@2.3.1: {} + + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + react-remove-scroll@2.7.2(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.15)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.15)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + + react-style-singleton@2.2.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + get-nonce: 1.0.1 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + react@19.2.6: {} + + robust-predicates@3.0.3: {} + + rolldown@1.2.1: + dependencies: + '@oxc-project/types': 0.142.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.1 + '@rolldown/binding-darwin-arm64': 1.2.1 + '@rolldown/binding-darwin-x64': 1.2.1 + '@rolldown/binding-freebsd-x64': 1.2.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.1 + '@rolldown/binding-linux-arm64-gnu': 1.2.1 + '@rolldown/binding-linux-arm64-musl': 1.2.1 + '@rolldown/binding-linux-ppc64-gnu': 1.2.1 + '@rolldown/binding-linux-s390x-gnu': 1.2.1 + '@rolldown/binding-linux-x64-gnu': 1.2.1 + '@rolldown/binding-linux-x64-musl': 1.2.1 + '@rolldown/binding-openharmony-arm64': 1.2.1 + '@rolldown/binding-wasm32-wasi': 1.2.1 + '@rolldown/binding-win32-arm64-msvc': 1.2.1 + '@rolldown/binding-win32-x64-msvc': 1.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + ts-api-utils@2.5.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + use-sidecar@1.1.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + + vite@8.2.0(jiti@2.7.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.25 + rolldown: 1.2.1 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.7.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} + + zustand@4.5.7(@types/react@19.2.15)(react@19.2.6): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + react: 19.2.6 diff --git a/tools/halidoscope/pnpm-workspace.yaml b/tools/halidoscope/pnpm-workspace.yaml new file mode 100644 index 000000000000..5ed0b5af0d45 --- /dev/null +++ b/tools/halidoscope/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: true diff --git a/tools/halidoscope/src-tauri/.gitignore b/tools/halidoscope/src-tauri/.gitignore new file mode 100644 index 000000000000..b21bd681d997 --- /dev/null +++ b/tools/halidoscope/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/tools/halidoscope/src-tauri/Cargo.lock b/tools/halidoscope/src-tauri/Cargo.lock new file mode 100644 index 000000000000..61a4ba92f546 --- /dev/null +++ b/tools/halidoscope/src-tauri/Cargo.lock @@ -0,0 +1,5595 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.12.1", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.117", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.12.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colorous" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e18bf7a165bf7028fde98609a0f1e8f7498d762a212598e6c891f6893556ec" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.12.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.12.1", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.12.1", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dbus" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.12.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash 0.2.0", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "embed-resource" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.2+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.12.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "halidoscope" +version = "0.1.0" +dependencies = [ + "bindgen", + "colorous", + "comfy-table", + "palette", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-cli", + "tauri-plugin-dialog", + "tauri-plugin-opener", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.12.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.12.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.12.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.12.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.12.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.12.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.12.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.12.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.12.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.12.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.12.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.12.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "open" +version = "5.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf 0.11.3", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.12.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.12.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.12.1", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-cli" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e78fb2c09a81546bcd376d34db4bda5769270d00990daa9f0d6e7ac1107e25" +dependencies = [ + "clap", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d3c1dbe38037e7f590cdf2492594d5ceebe031e7bc7e827509b22a999d2940" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf 0.13.1", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.2+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.12.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.12.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +dependencies = [ + "phf 0.13.1", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[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-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.12.1", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.3", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow 1.0.3", + "zvariant", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.3", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow 1.0.3", +] diff --git a/tools/halidoscope/src-tauri/Cargo.toml b/tools/halidoscope/src-tauri/Cargo.toml new file mode 100644 index 000000000000..fa4d693220cc --- /dev/null +++ b/tools/halidoscope/src-tauri/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "halidoscope" +version = "0.1.0" +description = "An interactive visualizer for Halide traces." +authors = ["Parker Ziegler "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "halidoscope_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } +bindgen = "0.72" + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +comfy-table = "7" +palette = "0.7.6" +colorous = "1.0.16" +tauri-plugin-dialog = "2" + +[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] +tauri-plugin-cli = "2.0.0" + +# `tauri dev` builds with the dev profile, whose default opt-level = 0 leaves the +# trace parser (a tight binary loop over millions of packets) badly unoptimized +# — multi-minute loads on large traces. Optimize codegen while keeping fast +# incremental builds and debuggability. +[profile.dev] +opt-level = 3 + +# Dependencies are compiled once and cached, so always optimize them fully. +[profile.dev.package."*"] +opt-level = 3 diff --git a/tools/halidoscope/src-tauri/build.rs b/tools/halidoscope/src-tauri/build.rs new file mode 100644 index 000000000000..f1adf986bbe3 --- /dev/null +++ b/tools/halidoscope/src-tauri/build.rs @@ -0,0 +1,42 @@ +use std::env; +use std::path::PathBuf; + +fn main() -> Result<(), Box> { + tauri_build::build(); + generate_trace_packet_bindings() +} + +/// Derives Rust type layouts for `halide_trace_packet_t` (and, transitively, the +/// `halide_trace_event_code_t` enum it references) directly from Halide's runtime header, so the +/// wire-format struct used by `trace.rs` stays mechanically in sync with the real C++ definition +/// instead of being hand-copied. +/// +/// This only derives *layout*: it deliberately does not generate bindings for any of +/// `halide_trace_packet_t`'s C++ methods (e.g., `coordinates()`, `value()`, `func()`). +fn generate_trace_packet_bindings() -> Result<(), Box> { + let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; + let header_path = PathBuf::from(&manifest_dir).join("../../../src/runtime/HalideRuntime.h"); + let header = header_path.canonicalize().map_err(|e| { + format!( + "HalideRuntime.h not found at {}: {e}", + header_path.display() + ) + })?; + + // Flag to cargo that we only need to rerun bindgen if HalideRuntime.h's contents have changed. + println!("cargo:rerun-if-changed={}", header.display()); + + let bindings = bindgen::Builder::default() + .header(header.to_string_lossy()) + .clang_args(["-x", "c++", "-std=c++17"]) + .allowlist_type("halide_trace_packet_t") + .with_codegen_config(bindgen::CodegenConfig::TYPES | bindgen::CodegenConfig::VARS) + .derive_default(true) + .generate()?; + + // Write generated bindings to this build's hashed OUT_DIR (as opposed to committing to source). + let out_path = PathBuf::from(env::var("OUT_DIR")?); + bindings.write_to_file(out_path.join("halide_trace_bindings.rs"))?; + + Ok(()) +} diff --git a/tools/halidoscope/src-tauri/capabilities/default.json b/tools/halidoscope/src-tauri/capabilities/default.json new file mode 100644 index 000000000000..b4320abb459a --- /dev/null +++ b/tools/halidoscope/src-tauri/capabilities/default.json @@ -0,0 +1,14 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "opener:default", + "cli:default", + "dialog:default" + ] +} diff --git a/tools/halidoscope/src-tauri/icons/128x128.png b/tools/halidoscope/src-tauri/icons/128x128.png new file mode 100644 index 000000000000..292c914497d5 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/128x128.png differ diff --git a/tools/halidoscope/src-tauri/icons/128x128@2x.png b/tools/halidoscope/src-tauri/icons/128x128@2x.png new file mode 100644 index 000000000000..e09e4d2fff80 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/128x128@2x.png differ diff --git a/tools/halidoscope/src-tauri/icons/32x32.png b/tools/halidoscope/src-tauri/icons/32x32.png new file mode 100644 index 000000000000..fdb79fcb4129 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/32x32.png differ diff --git a/tools/halidoscope/src-tauri/icons/64x64.png b/tools/halidoscope/src-tauri/icons/64x64.png new file mode 100644 index 000000000000..5623ff4a11e6 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/64x64.png differ diff --git a/tools/halidoscope/src-tauri/icons/icon.icns b/tools/halidoscope/src-tauri/icons/icon.icns new file mode 100644 index 000000000000..d999a58c8e18 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/icon.icns differ diff --git a/tools/halidoscope/src-tauri/icons/icon.ico b/tools/halidoscope/src-tauri/icons/icon.ico new file mode 100644 index 000000000000..7bdeaee593e5 Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/icon.ico differ diff --git a/tools/halidoscope/src-tauri/icons/icon.png b/tools/halidoscope/src-tauri/icons/icon.png new file mode 100644 index 000000000000..331bdf8a7fcf Binary files /dev/null and b/tools/halidoscope/src-tauri/icons/icon.png differ diff --git a/tools/halidoscope/src-tauri/src/cli.rs b/tools/halidoscope/src-tauri/src/cli.rs new file mode 100644 index 000000000000..41a5b36d55e5 --- /dev/null +++ b/tools/halidoscope/src-tauri/src/cli.rs @@ -0,0 +1,354 @@ +use std::ffi::OsStr; +use std::path::Path; + +use comfy_table::presets::UTF8_HORIZONTAL_ONLY; +use comfy_table::{presets, Table}; +use serde_json::json; +use tauri_plugin_cli::SubcommandMatches; + +use crate::commands::TraceMeta; +use crate::graph::to_dot; +use crate::render::{ + GrayscaleState, LoadFrequencyState, RedundantState, Renderer, ReuseDistanceState, RgbState, + StoreFrequencyState, +}; +use crate::trace::Trace; + +pub fn halidoscope_cli(subcommand: SubcommandMatches) { + match subcommand.name.as_str() { + "dot" => dot(subcommand), + "list" => list(subcommand), + "snapshot" => snapshot(subcommand), + "stats" => stats(subcommand), + cmd => { + eprintln!("Unknown subcommand {}", cmd); + std::process::exit(1); + } + }; +} + +fn dot(subcommand: SubcommandMatches) -> Option<()> { + let args = &subcommand.matches.args; + + let trace_path = args.get("trace").and_then(|a| a.value.as_str())?; + let destination = args.get("destination").and_then(|a| a.value.as_str()); + + // Load and parse the trace. + let trace = Trace::load_from_file(trace_path, |_, _| {}).unwrap_or_else(|e| { + eprintln!("Error loading trace: {}", e); + std::process::exit(1); + }); + let dot = to_dot(&trace.dag_edges); + + match destination { + Some(dest) => { + let ext = Path::new(dest).extension().and_then(OsStr::to_str); + + match ext { + Some("txt") | Some("gv") | Some("dot") => { + if let Err(e) = std::fs::write(dest, &dot) { + eprintln!("Failed to write DOT file: {}", e); + std::process::exit(1); + } + + println!("DOT file written to {}", dest); + std::process::exit(0); + } + _ => { + eprintln!( + "Unsupported file extension for DOT file, must be one of .txt, .gv, or .dot." + ); + std::process::exit(1); + } + } + } + None => { + println!("{}", dot); + std::process::exit(0); + } + } +} + +fn list(subcommand: SubcommandMatches) -> Option<()> { + let args = &subcommand.matches.args; + + let trace_path = args.get("trace").and_then(|a| a.value.as_str())?; + + // Load and parse the trace. + let trace = Trace::load_from_file(trace_path, |_, _| {}).unwrap_or_else(|e| { + eprintln!("Error loading trace: {}", e); + std::process::exit(1); + }); + + let as_json = args + .get("json") + .and_then(|a| a.value.as_bool()) + .unwrap_or(false); + + println!("Printing Funcs for {}", trace_path); + + if as_json { + let data = &trace + .funcs + .iter() + .map(|(name, func)| { + json!({ + "func": name.clone(), + "dimensionality": func.min_coords.len() + }) + }) + .collect::>(); + + let json = serde_json::to_string_pretty(&data).unwrap_or_else(|err| { + eprintln!("Error serializing Funcs to JSON: {}", err); + std::process::exit(1); + }); + + println!("{}", json); + } else { + let mut table = Table::new(); + + table + .load_preset(UTF8_HORIZONTAL_ONLY) + .set_header(vec!["Func", "Dimensionality"]); + + for (func_name, func) in &trace.funcs { + table.add_row(vec![func_name.clone(), func.min_coords.len().to_string()]); + } + + println!("{table}"); + } + + std::process::exit(0); +} + +fn stats(subcommand: SubcommandMatches) -> Option<()> { + let args = &subcommand.matches.args; + + let trace_path = args.get("trace").and_then(|a| a.value.as_str())?; + let func = args.get("func").and_then(|a| a.value.as_str()); + + // Load and parse the trace. + let trace = Trace::load_from_file(trace_path, |_, _| {}).unwrap_or_else(|e| { + eprintln!("Error loading trace: {}", e); + std::process::exit(1); + }); + + // Reuse the same derivation the GUI uses so the CLI reports identical stats. + let meta = TraceMeta::from_trace(&trace); + + let funcs: Vec<_> = match func { + Some(name) => { + let matched: Vec<_> = meta.funcs.iter().filter(|f| f.name == name).collect(); + + if matched.is_empty() { + eprintln!( + "Func '{}' not found in trace. Available Funcs: {:?}", + name, + meta.funcs.iter().map(|f| &f.name).collect::>() + ); + std::process::exit(1); + } + + matched + } + None => meta.funcs.iter().collect(), + }; + + let as_json = args.get("json").and_then(|a| a.value.as_bool())?; + + println!("Printing Func statistics for {}", trace_path); + + if as_json { + let json = serde_json::to_string_pretty(&funcs).unwrap_or_else(|err| { + eprintln!("Error serializing Func statistics to JSON: {}", err); + std::process::exit(1); + }); + println!("{}", json); + } else { + let mut table = Table::new(); + + table + .load_preset(presets::UTF8_HORIZONTAL_ONLY) + .set_header(vec![ + "Func", + "Minimum Coordinates", + "Maximum Coordinates", + "Minimum Value", + "Maximum Value", + "Maximum Store Count", + "Maximum Load Count", + "Thread Count", + ]); + + for func in funcs { + table.add_row(vec![ + func.name.clone(), + format!( + "({})", + func.min_coords + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + ), + format!( + "({})", + func.max_coords + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + ), + func.min_value.map(|v| v.to_string()).unwrap_or_default(), + func.max_value.map(|v| v.to_string()).unwrap_or_default(), + func.max_store_count.to_string(), + func.max_load_count.to_string(), + func.thread_count.to_string(), + ]); + } + println!("{table}"); + } + + std::process::exit(0); +} + +fn snapshot(subcommand: SubcommandMatches) -> Option<()> { + let args = &subcommand.matches.args; + + let trace_path = args.get("trace").and_then(|a| a.value.as_str())?; + let func = args.get("func").and_then(|a| a.value.as_str())?; + let packet_index = args + .get("packet-index") + .and_then(|a| a.value.as_str()) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + let mode = args + .get("mode") + .and_then(|a| a.value.as_str()) + .unwrap_or("grayscale"); + let destination = args.get("destination").and_then(|a| a.value.as_str())?; + + // Load and parse the trace. + let trace = Trace::load_from_file(trace_path, |_, _| {}).unwrap_or_else(|e| { + eprintln!("Error loading trace: {}", e); + std::process::exit(1); + }); + + // Find the target function. + let target_func = trace.funcs.get(func)?; + + // Exit early if the packet index is out of bounds. + if packet_index as usize >= trace.packets.len() { + eprintln!( + "Packet index {} is out of bounds. Valid range: 0..{}", + packet_index, + trace.packets.len() + ); + std::process::exit(1); + } + + let ext = Path::new(destination).extension().and_then(OsStr::to_str); + let store_indices = trace.func_store_indices(func)?; + let load_indices = trace.func_load_indices(func)?; + let k = packet_index.try_into().unwrap_or_else(|_| { + eprintln!("Packet index {} is too large.", packet_index); + std::process::exit(1); + }); + + match ext { + Some("json") => { + // Obtain the values for the given rendering mode at the current packet index. + let json = match mode { + "grayscale" => { + let Some(mut gs) = GrayscaleState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + gs.seek(&trace, store_indices, k); + serde_json::to_string_pretty(&gs.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + "rgb" => { + let Some(mut rgbs) = RgbState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + rgbs.seek(&trace, store_indices, k); + serde_json::to_string_pretty(&rgbs.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + "store-frequency" => { + let Some(mut sfs) = StoreFrequencyState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + sfs.seek(&trace, store_indices, k); + serde_json::to_string_pretty(&sfs.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + "load-frequency" => { + let Some(mut lfs) = LoadFrequencyState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + lfs.seek(&trace, load_indices, k); + serde_json::to_string_pretty(&lfs.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + "redundant-stores" => { + let Some(mut rs) = RedundantState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + rs.seek(&trace, store_indices, load_indices, k, k); + serde_json::to_string_pretty(&rs.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + "reuse-distance" => { + let Some(mut rds) = ReuseDistanceState::new(&trace, &target_func.name) else { + eprintln!("Func '{}' has no usable geometry.", target_func.name); + std::process::exit(1); + }; + + rds.seek(&trace, store_indices, load_indices, k, k); + serde_json::to_string_pretty(&rds.to_values()).unwrap_or_else(|e| { + eprintln!("Error serializing values to JSON: {}", e); + std::process::exit(1); + }) + } + _ => { + eprintln!("Unsupported rendering mode '{}'.", mode); + std::process::exit(1); + } + }; + + if let Err(e) = std::fs::write(destination, json) { + eprintln!("Failed to write snapshot file: {}", e); + std::process::exit(1); + } + + println!("Snapshot written to {}", destination); + std::process::exit(0); + } + _ => { + eprintln!("Unsupported file extension for snapshot file, must be .json."); + std::process::exit(1); + } + } +} diff --git a/tools/halidoscope/src-tauri/src/colormap.rs b/tools/halidoscope/src-tauri/src/colormap.rs new file mode 100644 index 000000000000..8792464b8e43 --- /dev/null +++ b/tools/halidoscope/src-tauri/src/colormap.rs @@ -0,0 +1,73 @@ +use palette::{Mix, Srgb}; + +/// The palette used for displaying core metrics in Halidoscope, including Store Frequency, Load +/// Frequency, Redundant Stores, and Reuse Distance. +pub const METRIC_PALETTE: [&str; 10] = [ + "#0078D1", "#1695F3", "#3DACFF", "#70C2FF", "#D6EEFF", "#FFE2D6", "#FFBFA3", "#FF773D", + "#FA6400", "#D64000", +]; + +pub struct Colormap { + stops: Vec>, +} + +impl Colormap { + pub fn from_hex(hex_colors: &[&str]) -> Self { + Self { + stops: hex_colors.iter().map(|hex| parse_hex(hex)).collect(), + } + } + + /// Samples the gradient at `i / 255` for `i` in `[0, 255]` to build an LUT. + pub fn to_lut(&self) -> [[u8; 3]; 256] { + std::array::from_fn(|i| self.eval(i as f64 / 255.0)) + } + + fn eval(&self, t: f64) -> [u8; 3] { + let segments = self.stops.len() - 1; + let t = (t.clamp(0.0, 1.0) as f32) * segments as f32; + let i = (t.floor() as usize).min(segments - 1); + let local_t = t - i as f32; + + let color: Srgb = self.stops[i].mix(self.stops[i + 1], local_t).into_format(); + [color.red, color.green, color.blue] + } +} + +fn parse_hex(hex: &str) -> Srgb { + let hex = hex.trim_start_matches('#'); + let r = u8::from_str_radix(&hex[0..2], 16).expect("valid hex color"); + let g = u8::from_str_radix(&hex[2..4], 16).expect("valid hex color"); + let b = u8::from_str_radix(&hex[4..6], 16).expect("valid hex color"); + + Srgb::new(r, g, b).into_format() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn matches_d3_piecewise_reference() { + let cmap = Colormap::from_hex(&METRIC_PALETTE); + let cases: [(f64, &str); 11] = [ + (0.0, "#0078d1"), + (0.01, "#027bd4"), + (0.12, "#1997f4"), + (0.33, "#6ec1ff"), + (0.495, "#e9e9ec"), + (0.50, "#ebe8eb"), + (0.505, "#ece7e9"), + (0.66, "#ffc1a6"), + (0.87, "#fb670a"), + (0.99, "#d94300"), + (1.0, "#d64000"), + ]; + + for (t, expected) in cases { + let [r, g, b] = cmap.eval(t); + let got = format!("#{r:02x}{g:02x}{b:02x}"); + assert_eq!(got, expected, "t={t}"); + } + } +} diff --git a/tools/halidoscope/src-tauri/src/commands.rs b/tools/halidoscope/src-tauri/src/commands.rs new file mode 100644 index 000000000000..864a74550df4 --- /dev/null +++ b/tools/halidoscope/src-tauri/src/commands.rs @@ -0,0 +1,706 @@ +//! Frontend-facing API contract for Halidoscope. +//! +//! This module owns the types that cross the Tauri IPC boundary. + +use std::collections::{BTreeMap, HashMap}; +use std::sync::Mutex; + +use serde::{Deserialize, Serialize}; +use tauri::ipc::Response; +use tauri::{AppHandle, Emitter, State}; + +use crate::render::{ + GrayscaleState, LoadFrequencyState, NormalizationMode, RedundantState, Renderer, + ReuseDistanceState, RgbState, StoreFrequencyState, ThreadOpMode, ThreadState, +}; +use crate::trace::Trace; + +/// A half-open packet-index interval `[start, end]` used for liveness and produce/consume ranges. +#[derive(Debug, Clone, Copy, Serialize)] +pub struct IndexRange { + pub start: u32, + pub end: u32, +} + +impl IndexRange { + fn from_tuple((start, end): (u32, u32)) -> Self { + Self { start, end } + } +} + +/// Per-Func metadata the frontend needs to size canvases and bound the scrub timeline. +#[derive(Debug, Clone, Serialize)] +pub struct FuncMeta { + pub name: String, + pub width: u32, + pub height: u32, + pub channels: u32, + pub min_coords: Vec, + pub max_coords: Vec, + pub min_value: Option, + pub max_value: Option, + pub max_store_count: u32, + pub max_load_count: u32, + pub max_redundant_store_count: u32, + pub max_reuse_distance: u64, + pub buffer_liveness: IndexRange, + pub produce_ranges: Vec, + pub consume_ranges: Vec, + pub thread_count: u32, + pub thread_ids: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StatsMeta { + global_max_store_count: u32, + global_max_load_count: u32, + global_max_redundant_store_count: u32, + global_max_reuse_distance: u64, + global_thread_ids: Vec, +} + +/// Top-level payload returned by `open_trace`. +#[derive(Debug, Clone, Serialize)] +pub struct TraceMeta { + pub funcs: Vec, + pub total_packets: u32, + pub dag_edges: BTreeMap>, + pub stats: StatsMeta, +} + +impl TraceMeta { + pub fn from_trace(trace: &Trace) -> Self { + let funcs = trace + .funcs + .iter() + .map(|(name, stats)| { + let geom = trace.func_geometry(name); + let (width, height, channels) = match geom { + Some(g) => (g.width as u32, g.height as u32, g.channels as u32), + None => (0, 0, 1), + }; + + FuncMeta { + name: name.clone(), + width, + height, + channels, + min_coords: stats.min_coords.clone(), + max_coords: stats.max_coords.clone(), + min_value: stats.min_value, + max_value: stats.max_value, + max_store_count: stats.max_store_count, + max_load_count: stats.max_load_count, + max_redundant_store_count: stats.max_redundant_store_count, + max_reuse_distance: stats.max_reuse_distance, + buffer_liveness: IndexRange::from_tuple( + *trace.func_buffer_liveness_range(name).unwrap_or(&(0, 0)), + ), + produce_ranges: trace + .func_produce_ranges(name) + .unwrap_or(&[]) + .iter() + .copied() + .map(IndexRange::from_tuple) + .collect(), + consume_ranges: trace + .func_consume_ranges(name) + .unwrap_or(&[]) + .iter() + .copied() + .map(IndexRange::from_tuple) + .collect(), + thread_count: trace + .func_thread_ids(name) + .map_or(1, |ids| ids.len() as u32), + thread_ids: trace + .func_thread_ids(name) + .map(|ids| ids.iter().map(|x| x.to_string()).collect()) + .unwrap_or_default(), + } + }) + .collect(); + + let dag_edges = trace + .dag_edges + .iter() + .map(|(consumer, producers)| (consumer.clone(), producers.iter().cloned().collect())) + .collect(); + + TraceMeta { + funcs, + total_packets: trace.packets.len() as u32, + dag_edges, + stats: StatsMeta { + global_max_store_count: trace.global_max_store_count, + global_max_load_count: trace.global_max_load_count, + global_max_redundant_store_count: trace.global_max_redundant_store_count, + global_max_reuse_distance: trace.global_max_reuse_distance, + global_thread_ids: trace + .global_thread_ids + .iter() + .map(|id| id.to_string()) + .collect(), + }, + } + } +} + +// ── Tauri-managed state ─────────────────────────────────────────────────────── + +/// The currently loaded trace plus per-Func render caches, one map per rendering pathway. Each +/// cache keeps its Func's state warm across requests so forward scrubbing only applies the delta. +struct Loaded { + trace: Trace, + grayscale_renderers: HashMap, + rgb_renderers: HashMap, + store_frequency_renderers: HashMap, + load_frequency_renderers: HashMap, + redundant_renderers: HashMap, + reuse_distance_renderers: HashMap, + thread_renderers: HashMap, +} + +/// App-wide state managed by Tauri. A single trace is loaded at a time; opening a new one replaces +/// it (and drops all stale render caches). +#[derive(Default)] +pub struct AppState { + inner: Mutex>, +} + +/// Packs tensor data, tabular data, and NaN / Inf data in a single IPC response. +fn pack_render_response( + mut pixels: Vec, + nan_overlay: Vec, + inf_overlay: Vec, + tabular_data: Vec, +) -> Vec { + pixels.reserve(nan_overlay.len() + inf_overlay.len() + tabular_data.len() * 4); + + pixels.extend_from_slice(&nan_overlay); + pixels.extend_from_slice(&inf_overlay); + + for v in tabular_data { + pixels.extend_from_slice(&v.to_le_bytes()); + } + + pixels +} + +// ── Commands ────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, Serialize)] +struct TraceProgress { + message: String, + progress: u8, +} + +/// Parses a `.hltrace` file and returns the metadata the frontend needs to set up canvases and +/// the scrub timeline. Replaces any previously loaded trace. +/// +/// Runs the parse on a blocking-task thread rather than the main thread. Running async on a +/// separate thread prevents us from blocking the webview's event loop and freezing the UI. +/// Progress (percentage of bytes parsed) is emitted to the frontend as `trace-load-progress` events. +#[tauri::command] +pub async fn open_trace( + path: String, + app: AppHandle, + state: State<'_, AppState>, +) -> Result { + let (trace, meta) = tauri::async_runtime::spawn_blocking(move || { + let trace = Trace::load_from_file(&path, |message, progress| { + let _ = app.emit("trace-load-progress", TraceProgress { message, progress }); + })?; + + let meta = TraceMeta::from_trace(&trace); + + Ok::<_, String>((trace, meta)) + }) + .await + .map_err(|e| e.to_string())??; + + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + *guard = Some(Loaded { + trace, + grayscale_renderers: HashMap::new(), + rgb_renderers: HashMap::new(), + store_frequency_renderers: HashMap::new(), + load_frequency_renderers: HashMap::new(), + redundant_renderers: HashMap::new(), + reuse_distance_renderers: HashMap::new(), + thread_renderers: HashMap::new(), + }); + Ok(meta) +} + +/// Renders `func` as a grayscale image at `global_index` and returns raw RGBA8 bytes. Channel 0 +/// is normalized to [0, 255] and replicated across R/G/B. +#[tauri::command] +pub fn render_grayscale( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + grayscale_renderers, + .. + } = loaded; + + if !grayscale_renderers.contains_key(&func) { + let rs = GrayscaleState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + grayscale_renderers.insert(func.clone(), rs); + } + let renderer = grayscale_renderers.get_mut(&func).expect("just inserted"); + + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let k = store_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_histogram() + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +/// Renders `func` as an RGB image at `global_index` and returns raw RGBA8 bytes. Planes 0/1/2 +/// map to R/G/B; missing planes default to 0. +#[tauri::command] +pub fn render_rgb( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + rgb_renderers, + .. + } = loaded; + + if !rgb_renderers.contains_key(&func) { + let rs = RgbState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + rgb_renderers.insert(func.clone(), rs); + } + let renderer = rgb_renderers.get_mut(&func).expect("just inserted"); + + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let k = store_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_histogram() + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +/// Renders a heatmap of store counts for `func` up to `global_index` and returns raw RGBA8 bytes. +#[tauri::command] +pub fn render_store_frequency( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + store_frequency_renderers, + .. + } = loaded; + + if !store_frequency_renderers.contains_key(&func) { + let hs = StoreFrequencyState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + store_frequency_renderers.insert(func.clone(), hs); + } + let renderer = store_frequency_renderers + .get_mut(&func) + .expect("just inserted"); + + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let k = store_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_tabular_data(normalization_mode) + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +/// Renders a heatmap of load counts for `func` up to `global_index` and returns raw RGBA8 bytes. +#[tauri::command] +pub fn render_load_frequency( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + load_frequency_renderers, + .. + } = loaded; + + if !load_frequency_renderers.contains_key(&func) { + let hs = LoadFrequencyState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + load_frequency_renderers.insert(func.clone(), hs); + } + let renderer = load_frequency_renderers + .get_mut(&func) + .expect("just inserted"); + + let load_indices = trace.func_load_indices(&func).unwrap_or(&[]); + let k = load_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, load_indices, k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_tabular_data(normalization_mode) + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +/// Renders a heatmap of redundant store counts for `func` up to `global_index` and returns raw +/// RGBA8 bytes. A store is redundant when it writes the same value to a location that already +/// holds that value _and_ no intervening load has read that value. +#[tauri::command] +pub fn render_redundant_stores( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + redundant_renderers, + .. + } = loaded; + + if !redundant_renderers.contains_key(&func) { + let rs = RedundantState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + redundant_renderers.insert(func.clone(), rs); + } + let renderer = redundant_renderers.get_mut(&func).expect("just inserted"); + + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let load_indices = trace.func_load_indices(&func).unwrap_or(&[]); + let store_k = store_indices.partition_point(|&p| p <= global_index as usize); + let load_k = load_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, load_indices, store_k, load_k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_tabular_data(normalization_mode) + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +/// Renders a heatmap of maximum store-to-load reuse distances for `func` up to `global_index` +/// and returns raw RGBA8 bytes. Reuse distance is the number of packets elapsed between a store +/// and the next load from the same (x, y, channel). +#[tauri::command] +pub fn render_reuse_distance( + func: String, + global_index: u32, + normalization_mode: NormalizationMode, + include_tabular_data: bool, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + reuse_distance_renderers, + .. + } = loaded; + + if !reuse_distance_renderers.contains_key(&func) { + let rs = ReuseDistanceState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + reuse_distance_renderers.insert(func.clone(), rs); + } + let renderer = reuse_distance_renderers + .get_mut(&func) + .expect("just inserted"); + + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let load_indices = trace.func_load_indices(&func).unwrap_or(&[]); + let store_k = store_indices.partition_point(|&p| p <= global_index as usize); + let load_k = load_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, load_indices, store_k, load_k); + + let pixels = renderer.to_rgba(normalization_mode); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let histogram = if include_tabular_data { + renderer.to_tabular_data(normalization_mode) + } else { + Vec::new() + }; + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + histogram, + ))) +} + +#[tauri::command] +pub fn render_thread( + func: String, + global_index: u32, + op_mode: ThreadOpMode, + thread_id: String, + include_nan: bool, + include_inf: bool, + state: State, +) -> Result { + let mut guard = state.inner.lock().map_err(|e| e.to_string())?; + let loaded = guard.as_mut().ok_or("no trace loaded")?; + let Loaded { + trace, + thread_renderers, + .. + } = loaded; + + if !thread_renderers.contains_key(&func) { + let rs = ThreadState::new(trace, &func) + .ok_or_else(|| format!("func '{func}' has no renderable geometry"))?; + thread_renderers.insert(func.clone(), rs); + } + let renderer = thread_renderers.get_mut(&func).expect("just inserted"); + let store_indices = trace.func_store_indices(&func).unwrap_or(&[]); + let load_indices = trace.func_load_indices(&func).unwrap_or(&[]); + let store_k = store_indices.partition_point(|&p| p <= global_index as usize); + let load_k = load_indices.partition_point(|&p| p <= global_index as usize); + renderer.seek(trace, store_indices, load_indices, store_k, load_k, op_mode); + + let pixels = renderer.to_rgba(thread_id); + + let nan_overlay = if include_nan { + renderer.to_nan_overlay() + } else { + Vec::new() + }; + + let inf_overlay = if include_inf { + renderer.to_inf_overlay() + } else { + Vec::new() + }; + + let (store_counts, load_counts) = renderer.to_thread_counts(); + let thread_counts: Vec = store_counts.iter().chain(load_counts).copied().collect(); + + Ok(Response::new(pack_render_response( + pixels, + nan_overlay, + inf_overlay, + thread_counts, + ))) +} + +// ── Profiler ───────────────────────────────────────────────────────────────────────────────────── +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ProfileFunc { + name: String, + parent: i32, + canonical_id: u32, + kind: u32, + buffer_func_id: i32, + time_ns: u64, + memory_current: u64, + memory_peak: u64, + memory_total: u64, + stack_peak: u64, + active_threads_numerator: u32, + active_threads_denominator: u32, + num_allocs: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ProfilePipeline { + name: String, + runs: u32, + billed_runs: u32, + samples: u32, + num_allocs: u32, + time_ns: u64, + memory_current: u64, + memory_peak: u64, + memory_total: u64, + active_threads_numerator: u32, + active_threads_denominator: u32, + funcs: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Profile { + pipelines: Vec, +} + +impl Profile { + fn from_profile(path: &str) -> Result { + let data = std::fs::read(path).map_err(|e| e.to_string())?; + serde_json::from_slice(&data).map_err(|e| e.to_string()) + } +} + +#[tauri::command] +pub fn open_profile(path: &str) -> Result { + let profile = Profile::from_profile(path)?; + + Ok(profile) +} diff --git a/tools/halidoscope/src-tauri/src/graph.rs b/tools/halidoscope/src-tauri/src/graph.rs new file mode 100644 index 000000000000..c4843aabbe22 --- /dev/null +++ b/tools/halidoscope/src-tauri/src/graph.rs @@ -0,0 +1,16 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::Write; + +pub fn to_dot(dag_edges: &BTreeMap>) -> String { + let mut dot = String::from("strict digraph {\n\trankdir = LR\n\n"); + + for (key, value) in dag_edges.iter() { + for dest in value { + writeln!(dot, "\t{key} -> {dest}").unwrap_or_default(); + } + } + + write!(dot, "}}").unwrap_or_default(); + + dot +} diff --git a/tools/halidoscope/src-tauri/src/lib.rs b/tools/halidoscope/src-tauri/src/lib.rs new file mode 100644 index 000000000000..6552fb526d7d --- /dev/null +++ b/tools/halidoscope/src-tauri/src/lib.rs @@ -0,0 +1,78 @@ +use tauri_plugin_cli::CliExt; + +use crate::cli::halidoscope_cli; + +pub mod cli; +pub mod colormap; +pub mod commands; +pub mod graph; +pub mod render; +pub mod trace; + +#[tauri::command] +fn get_cwd() -> Result { + let cwd = std::env::current_dir().map_err(|e| e.to_string())?; + + // In dev, `cargo run` is invoked with its cwd set to `src-tauri`, so walk + // up one level to match the directory the user launched `tauri dev` from. + // The bundled binary is launched directly, so its cwd needs no adjustment. + if cfg!(debug_assertions) { + cwd.parent() + .map(|parent| parent.to_string_lossy().into_owned()) + .ok_or_else(|| "no parent directory".to_string()) + } else { + Ok(cwd.to_string_lossy().into_owned()) + } +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_cli::init()) + .setup(|app| { + match app.cli().matches() { + Ok(matches) => { + if let Some(help) = matches.args.get("help").and_then(|a| a.value.as_str()) { + println!("{}", help); + std::process::exit(0); + } + if matches.args.contains_key("version") { + println!("{}", app.package_info().version); + std::process::exit(0); + } + match matches.subcommand { + Some(subcommand) => halidoscope_cli(*subcommand), + None => { + tauri::WebviewWindowBuilder::from_config( + app.handle(), + &app.config().app.windows[0], + )? + .build()?; + } + } + } + Err(e) => { + eprintln!("Error parsing CLI arguments: {}", e); + std::process::exit(1); + } + } + Ok(()) + }) + .plugin(tauri_plugin_opener::init()) + .manage(commands::AppState::default()) + .invoke_handler(tauri::generate_handler![ + get_cwd, + commands::open_trace, + commands::render_grayscale, + commands::render_rgb, + commands::render_store_frequency, + commands::render_load_frequency, + commands::render_redundant_stores, + commands::render_reuse_distance, + commands::render_thread, + commands::open_profile, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/tools/halidoscope/src-tauri/src/main.rs b/tools/halidoscope/src-tauri/src/main.rs new file mode 100644 index 000000000000..a9b01cbb75ec --- /dev/null +++ b/tools/halidoscope/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + halidoscope_lib::run() +} diff --git a/tools/halidoscope/src-tauri/src/render.rs b/tools/halidoscope/src-tauri/src/render.rs new file mode 100644 index 000000000000..535e8b902e2d --- /dev/null +++ b/tools/halidoscope/src-tauri/src/render.rs @@ -0,0 +1,1345 @@ +use std::vec; + +use ::colorous; +use serde::Deserialize; + +use crate::colormap::{Colormap, METRIC_PALETTE}; +use crate::trace::{for_each_lane_pixel, FuncGeometry, Trace, TracePacket}; + +#[derive(Deserialize, Clone, Copy)] +pub enum NormalizationMode { + #[serde(rename = "Across Funcs")] + AcrossFuncs, + #[serde(rename = "Per Func")] + PerFunc, +} + +// A trait that all 2D Canvas renderers implement. +pub trait Renderer: Sized { + type Value; + + fn seek(&mut self, trace: &Trace, store_indices: &[usize], target_k: usize); + fn to_rgba(&self, normalization_mode: NormalizationMode) -> Vec; + fn to_nan_overlay(&self) -> Vec; + fn to_inf_overlay(&self) -> Vec; + fn to_values(&self) -> Vec; +} + +/// Packs a per-pixel predicate over channel values into a 1-bit-per-pixel mask: bit 1 for a pixel +/// where `is_set` holds for any channel, bit 0 otherwise. Bits are packed MSB-first within each +/// byte, in the same row-major order as `values`; the final byte is zero-padded if the pixel count +/// isn't a multiple of 8. The frontend expands this back into a colored RGBA8 overlay, since every +/// set pixel shares the same user-selected overlay color. +fn pack_mask(values: &[f64], channels: usize, is_set: impl Fn(&[f64]) -> bool) -> Vec { + let num_pixels = values.len() / channels; + let mut out = vec![0u8; num_pixels.div_ceil(8)]; + + for (i, src) in values.chunks_exact(channels).enumerate() { + if is_set(src) { + out[i / 8] |= 1 << (7 - (i % 8)); + } + } + + out +} + +fn nan_mask(values: &[f64], channels: usize) -> Vec { + pack_mask(values, channels, |src| src.iter().any(|v| v.is_nan())) +} + +fn inf_mask(values: &[f64], channels: usize) -> Vec { + pack_mask(values, channels, |src| src.iter().any(|v| v.is_infinite())) +} + +// ── Grayscale rendering ────────────────────────────────────────────────────────────────────────── + +pub struct GrayscaleState { + geom: FuncGeometry, + min_v: f64, + max_v: f64, + framebuffer: Vec, + values: Vec, + applied_k: usize, +} + +impl GrayscaleState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let stats = trace.funcs.get(func)?; + let min_v = stats.min_value.unwrap_or(0.0); + let max_v = stats.max_value.unwrap_or(255.0); + let framebuffer = vec![0u8; geom.width * geom.height * geom.channels]; + let values = vec![0f64; geom.width * geom.height * geom.channels]; + + Some(Self { + geom, + min_v, + max_v, + framebuffer, + values, + applied_k: 0, + }) + } + + fn apply_store(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, _pixel_idx, val_idx| { + if let Some(v) = pkt.decoded_value(lane) { + self.framebuffer[val_idx] = self.normalize(v); + self.values[val_idx] = v; + }; + }, + ); + } + + #[inline] + fn normalize(&self, v: f64) -> u8 { + (255.0 * (v - self.min_v) / (self.max_v - self.min_v)).clamp(0.0, 255.0) as u8 + } + + /// Bins the raw (pre-normalization) intensity displayed per pixel — the same luma blend + /// `to_rgba` uses for channels >= 3, or the raw channel-0 value otherwise — into 256 + /// fixed-width buckets (one per displayable 8-bit gray level) spanning `[min_v, max_v]`. + pub fn to_histogram(&self) -> Vec { + const NUM_BINS: usize = 256; + let channels = self.geom.channels; + let range = self.max_v - self.min_v; + + let mut bins = vec![0u32; NUM_BINS]; + for src in self.values.chunks_exact(channels) { + let v = if channels >= 3 { + src[0] * 0.2125 + src[1] * 0.7154 + src[2] * 0.0721 + } else { + src[0] + }; + + let bucket = if range > 0.0 { + (((v - self.min_v) / range) * NUM_BINS as f64) as usize + } else { + 0 + }; + bins[bucket.min(NUM_BINS - 1)] += 1; + } + + bins + } +} + +impl Renderer for GrayscaleState { + type Value = f64; + + fn seek(&mut self, trace: &Trace, store_indices: &[usize], target_k: usize) { + let target_k = target_k.min(store_indices.len()); + if target_k < self.applied_k { + self.framebuffer.iter_mut().for_each(|b| *b = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_k = 0; + } + + for &global_idx in &store_indices[self.applied_k..target_k] { + self.apply_store(&trace.packets[global_idx]); + } + + self.applied_k = target_k; + } + + fn to_rgba(&self, _normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { + width, + height, + channels, + .. + } = self.geom; + let mut out = vec![0u8; width * height * 4]; + let fb = &self.framebuffer; + if channels >= 3 { + for (chunk, src) in out.chunks_exact_mut(4).zip(fb.chunks_exact(channels)) { + // Use grayscale weights from scikit-image: + // https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_rgb_to_gray.html + let gray = src[0] as f64 * 0.2125 + src[1] as f64 * 0.7154 + src[2] as f64 * 0.0721; + chunk[0] = gray as u8; + chunk[1] = gray as u8; + chunk[2] = gray as u8; + chunk[3] = 255; + } + } else { + for (chunk, src) in out.chunks_exact_mut(4).zip(fb.chunks_exact(channels)) { + chunk[0] = src[0]; + chunk[1] = src[0]; + chunk[2] = src[0]; + chunk[3] = 255; + } + } + + out + } + + fn to_values(&self) -> Vec { + self.values.clone() + } + + fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} + +// ── RGB rendering ──────────────────────────────────────────────────────────────────────────────── + +pub struct RgbState { + geom: FuncGeometry, + min_v: f64, + max_v: f64, + framebuffer: Vec, + values: Vec, + applied_k: usize, +} + +impl RgbState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let stats = trace.funcs.get(func)?; + let min_v = stats.min_value.unwrap_or(0.0); + let max_v = stats.max_value.unwrap_or(255.0); + let framebuffer = vec![0u8; geom.width * geom.height * geom.channels]; + let values = vec![0f64; geom.width * geom.height * geom.channels]; + + Some(Self { + geom, + min_v, + max_v, + framebuffer, + values, + applied_k: 0, + }) + } + + fn apply_store(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, _pixel_idx, val_idx| { + if let Some(v) = pkt.decoded_value(lane) { + self.framebuffer[val_idx] = self.normalize(v); + self.values[val_idx] = v; + }; + }, + ); + } + + #[inline] + fn normalize(&self, v: f64) -> u8 { + (255.0 * (v - self.min_v) / (self.max_v - self.min_v)).clamp(0.0, 255.0) as u8 + } + + /// Bins the raw (pre-normalization) per-channel values into 256 fixed-width buckets spanning + /// `[min_v, max_v]`. When `channels >= 3`, returns three histograms back to back (R, then G, + /// then B, 256 `u32`s each — the caller recovers the channel count as `len / 256`). Otherwise + /// falls back to a single histogram over channel 0, matching `GrayscaleState::to_histogram`. + pub fn to_histogram(&self) -> Vec { + const NUM_BINS: usize = 256; + let channels = self.geom.channels; + let range = self.max_v - self.min_v; + + let bucket_of = |v: f64| -> usize { + let bucket = if range > 0.0 { + (((v - self.min_v) / range) * NUM_BINS as f64) as usize + } else { + 0 + }; + bucket.min(NUM_BINS - 1) + }; + + if channels < 3 { + let mut bins = vec![0u32; NUM_BINS]; + for src in self.values.chunks_exact(channels) { + bins[bucket_of(src[0])] += 1; + } + return bins; + } + + let mut bins = vec![0u32; NUM_BINS * 3]; + for src in self.values.chunks_exact(channels) { + for c in 0..3 { + bins[c * NUM_BINS + bucket_of(src[c])] += 1; + } + } + bins + } +} + +impl Renderer for RgbState { + type Value = f64; + + fn seek(&mut self, trace: &Trace, store_indices: &[usize], target_k: usize) { + let target_k = target_k.min(store_indices.len()); + if target_k < self.applied_k { + self.framebuffer.iter_mut().for_each(|b| *b = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_k = 0; + } + + for &global_idx in &store_indices[self.applied_k..target_k] { + self.apply_store(&trace.packets[global_idx]); + } + + self.applied_k = target_k; + } + + fn to_rgba(&self, _normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { + width, + height, + channels, + .. + } = self.geom; + let mut out = vec![0u8; width * height * 4]; + let fb = &self.framebuffer; + if channels >= 3 { + for (chunk, src) in out.chunks_exact_mut(4).zip(fb.chunks_exact(channels)) { + chunk[0] = src[0]; + chunk[1] = src[1]; + chunk[2] = src[2]; + chunk[3] = 255; + } + } else { + for (chunk, src) in out.chunks_exact_mut(4).zip(fb.chunks_exact(channels)) { + chunk[0] = src[0]; + chunk[1] = src[0]; + chunk[2] = src[0]; + chunk[3] = 255; + } + } + + out + } + + fn to_values(&self) -> Vec { + self.values.clone() + } + + fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} + +// ── Store frequency rendering ──────────────────────────────────────────────────────────────────── + +pub struct StoreFrequencyState { + geom: FuncGeometry, + counts: Vec, + values: Vec, + local_max_store_count: u32, + global_max_store_count: u32, + applied_k: usize, +} + +impl StoreFrequencyState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let counts = vec![0u32; geom.width * geom.height]; + let values = vec![0f64; geom.width * geom.height * geom.channels]; + + let local_max_store_count = trace.funcs.get(func).map_or(0, |s| s.max_store_count); + let global_max_store_count = trace + .funcs + .values() + .map(|s| s.max_store_count) + .max() + .unwrap_or(0); + + Some(Self { + geom, + counts, + values, + local_max_store_count, + global_max_store_count, + applied_k: 0, + }) + } + + fn increment_pixel(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, pixel_idx, val_idx| { + self.counts[pixel_idx] += 1; + + if let Some(v) = pkt.decoded_value(lane) { + self.values[val_idx] = v; + } + }, + ); + } + + pub fn to_tabular_data(&self, normalization_mode: NormalizationMode) -> Vec { + let max = match normalization_mode { + NormalizationMode::AcrossFuncs => self.global_max_store_count, + NormalizationMode::PerFunc => self.local_max_store_count, + }; + + let exceeds_max_bins = max > 64; + // Pre-allocate tabular_data, capping to 64 bins. + let mut tabular_data = vec![ + 0u32; + if exceeds_max_bins { + 64 + } else { + max as usize + 1 + } + ]; + + for &c in &self.counts { + let bucket = if exceeds_max_bins { c * 63 / max } else { c }; + + tabular_data[bucket.clamp(0, 63) as usize] += 1; + } + + tabular_data + } +} + +impl Renderer for StoreFrequencyState { + type Value = u32; + + fn seek(&mut self, trace: &Trace, store_indices: &[usize], target_k: usize) { + let target_k = target_k.min(store_indices.len()); + if target_k < self.applied_k { + self.counts.iter_mut().for_each(|c| *c = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_k = 0; + } + for &idx in &store_indices[self.applied_k..target_k] { + self.increment_pixel(&trace.packets[idx]); + } + self.applied_k = target_k; + } + + fn to_rgba(&self, normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { width, height, .. } = self.geom; + let lut = Colormap::from_hex(&METRIC_PALETTE).to_lut(); + + let scale = match (normalization_mode, self.global_max_store_count) { + (NormalizationMode::AcrossFuncs, 0) => 0.0, + (NormalizationMode::AcrossFuncs, global_max) => 255.0 / global_max as f64, + (NormalizationMode::PerFunc, _) => { + let local_max = self.local_max_store_count; + if local_max > 0 { + 255.0 / local_max as f64 + } else { + 0.0 + } + } + }; + + let mut out = vec![0u8; width * height * 4]; + for (chunk, &count) in out.chunks_exact_mut(4).zip(self.counts.iter()) { + let ti = (count as f64 * scale) as usize; + let [r, g, b] = lut[ti.min(255)]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + chunk[3] = 255; + } + out + } + + fn to_values(&self) -> Vec { + self.counts.clone() + } + + fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} + +// ── Load frequency rendering ───────────────────────────────────────────────────────────────────── + +pub struct LoadFrequencyState { + geom: FuncGeometry, + counts: Vec, + values: Vec, + local_max_load_count: u32, + global_max_load_count: u32, + applied_k: usize, +} + +impl LoadFrequencyState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let counts = vec![0u32; geom.width * geom.height]; + let values = vec![0f64; geom.width * geom.height * geom.channels]; + + let local_max_load_count = trace.funcs.get(func).map_or(0, |s| s.max_load_count); + let global_max_load_count = trace + .funcs + .values() + .map(|s| s.max_load_count) + .max() + .unwrap_or(0); + + Some(Self { + geom, + counts, + values, + local_max_load_count, + global_max_load_count, + applied_k: 0, + }) + } + + fn increment_pixel(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, pixel_idx, val_idx| { + self.counts[pixel_idx] += 1; + + if let Some(v) = pkt.decoded_value(lane) { + self.values[val_idx] = v; + } + }, + ); + } + + pub fn to_tabular_data(&self, normalization_mode: NormalizationMode) -> Vec { + let max = match normalization_mode { + NormalizationMode::AcrossFuncs => self.global_max_load_count, + NormalizationMode::PerFunc => self.local_max_load_count, + }; + + let exceeds_max_bins = max > 64; + // Pre-allocate tabular_data, capping to 64 bins. + let mut tabular_data = vec![ + 0u32; + if exceeds_max_bins { + 64 + } else { + max as usize + 1 + } + ]; + + for &c in &self.counts { + let bucket = if exceeds_max_bins { c * 63 / max } else { c }; + + tabular_data[bucket.clamp(0, 63) as usize] += 1; + } + + tabular_data + } +} + +impl Renderer for LoadFrequencyState { + type Value = u32; + + fn seek(&mut self, trace: &Trace, load_indices: &[usize], target_k: usize) { + let target_k = target_k.min(load_indices.len()); + if target_k < self.applied_k { + self.counts.iter_mut().for_each(|c| *c = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_k = 0; + } + for &idx in &load_indices[self.applied_k..target_k] { + self.increment_pixel(&trace.packets[idx]); + } + self.applied_k = target_k; + } + + fn to_rgba(&self, normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { width, height, .. } = self.geom; + let lut = Colormap::from_hex(&METRIC_PALETTE).to_lut(); + + let scale = match (normalization_mode, self.global_max_load_count) { + (NormalizationMode::AcrossFuncs, 0) => 0.0, + (NormalizationMode::AcrossFuncs, global_max) => 255.0 / global_max as f64, + (NormalizationMode::PerFunc, _) => { + if self.local_max_load_count > 0 { + 255.0 / self.local_max_load_count as f64 + } else { + 0.0 + } + } + }; + + let mut out = vec![0u8; width * height * 4]; + for (chunk, &count) in out.chunks_exact_mut(4).zip(self.counts.iter()) { + let ti = (count as f64 * scale) as usize; + let [r, g, b] = lut[ti.min(255)]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + chunk[3] = 255; + } + out + } + + fn to_values(&self) -> Vec { + self.counts.clone() + } + + fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} + +// ── Redundant store rendering ──────────────────────────────────────────────────────────────────── + +pub struct RedundantState { + geom: FuncGeometry, + last_values: Vec>, + redundant_store_counts: Vec, + local_max_redundant_store_count: u32, + global_max_redundant_store_count: u32, + applied_store_k: usize, + applied_load_k: usize, +} + +impl RedundantState { + /// Builds an empty redundant state for `func`, or `None` if the Func has no usable geometry. + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let n_pixels = geom.width * geom.height; + + let local_max_redundant_store_count = + trace.funcs.get(func).map(|s| s.max_redundant_store_count)?; + let global_max_redundant_store_count = trace + .funcs + .values() + .map(|s| s.max_redundant_store_count) + .max()?; + + Some(Self { + geom, + last_values: vec![None; n_pixels * geom.channels], + redundant_store_counts: vec![0u32; n_pixels], + local_max_redundant_store_count, + global_max_redundant_store_count, + applied_store_k: 0, + applied_load_k: 0, + }) + } + + fn reset(&mut self) { + self.last_values.iter_mut().for_each(|v| *v = None); + self.redundant_store_counts.iter_mut().for_each(|c| *c = 0); + self.applied_store_k = 0; + self.applied_load_k = 0; + } + + fn apply_store(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, pixel_idx: usize, val_idx: usize| { + let Some(v) = pkt.decoded_value(lane) else { + return; + }; + + let v_bits = v.to_bits(); + if let Some(prev_bits) = self.last_values[val_idx] { + if prev_bits == v_bits { + self.redundant_store_counts[pixel_idx] += 1; + } + } + self.last_values[val_idx] = Some(v_bits); + }, + ); + } + + /// A load observes the current value at `(x, y, channel)`, so it breaks the redundancy chain: + /// clear the last-written value there so a subsequent store is never counted as redundant + /// against a value that predates the load. + fn apply_load(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |_lane, _pixel_idx, val_idx: usize| { + self.last_values[val_idx] = None; + }, + ); + } + + /// Seeks to the state after the first `target_store_k` stores and `target_load_k` loads. + /// Events are replayed in global packet order via a two-pointer merge of the two sorted index + /// lists. Backward seeks (either counter regresses) reset and replay from zero. + pub fn seek( + &mut self, + trace: &Trace, + store_indices: &[usize], + load_indices: &[usize], + target_store_k: usize, + target_load_k: usize, + ) { + let target_store_k = target_store_k.min(store_indices.len()); + let target_load_k = target_load_k.min(load_indices.len()); + + if target_store_k < self.applied_store_k || target_load_k < self.applied_load_k { + self.reset(); + } + + let store_slice = &store_indices[self.applied_store_k..target_store_k]; + let load_slice = &load_indices[self.applied_load_k..target_load_k]; + let mut si = 0; + let mut li = 0; + + while si < store_slice.len() || li < load_slice.len() { + let next_is_store = si < store_slice.len() + && (li >= load_slice.len() || store_slice[si] < load_slice[li]); + + if next_is_store { + self.apply_store(&trace.packets[store_slice[si]]); + si += 1; + } else { + self.apply_load(&trace.packets[load_slice[li]]); + li += 1; + } + } + + self.applied_store_k = target_store_k; + self.applied_load_k = target_load_k; + } + + pub fn to_rgba(&self, normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { width, height, .. } = self.geom; + + let lut = Colormap::from_hex(&METRIC_PALETTE).to_lut(); + + let scale = match (normalization_mode, self.global_max_redundant_store_count) { + (NormalizationMode::AcrossFuncs, 0) => 0.0, + (NormalizationMode::AcrossFuncs, global_max) => 255.0 / global_max as f64, + (NormalizationMode::PerFunc, _) => { + if self.local_max_redundant_store_count > 0 { + 255.0 / self.local_max_redundant_store_count as f64 + } else { + 0.0 + } + } + }; + + let mut out = vec![0u8; width * height * 4]; + for (chunk, &count) in out + .chunks_exact_mut(4) + .zip(self.redundant_store_counts.iter()) + { + if count > 0 { + let ti = (count as f64 * scale) as usize; + let [r, g, b] = lut[ti.min(255)]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + } + chunk[3] = 255; + } + out + } + + pub fn to_tabular_data(&self, normalization_mode: NormalizationMode) -> Vec { + let max = match normalization_mode { + NormalizationMode::AcrossFuncs => self.global_max_redundant_store_count, + NormalizationMode::PerFunc => self.local_max_redundant_store_count, + }; + + let exceeds_max_bins = max > 64; + // Pre-allocate tabular_data, capping to 64 bins. + let mut tabular_data = vec![ + 0u32; + if exceeds_max_bins { + 64 + } else { + max as usize + 1 + } + ]; + + for &c in &self.redundant_store_counts { + let bucket = if exceeds_max_bins { c * 63 / max } else { c }; + + tabular_data[bucket.clamp(0, 63) as usize] += 1; + } + + tabular_data + } + + pub fn to_values(&self) -> Vec { + self.redundant_store_counts.clone() + } + + pub fn to_nan_overlay(&self) -> Vec { + let values: Vec = self + .last_values + .iter() + .map(|v| v.map_or(0.0, f64::from_bits)) + .collect(); + + nan_mask(&values, self.geom.channels) + } + + pub fn to_inf_overlay(&self) -> Vec { + let values: Vec = self + .last_values + .iter() + .map(|v| v.map_or(0.0, f64::from_bits)) + .collect(); + + inf_mask(&values, self.geom.channels) + } +} + +// ── Reuse distance rendering ───────────────────────────────────────────────────────────────────── + +/// Per-pixel maximum reuse distance for one Func, seekable along the global timeline. +/// +/// For intermediate Funcs (those with stores) the anchor is the most recent store per +/// `(x, y, channel)`; reuse distance is measured to the next load from the same location. +/// +/// For pipeline inputs (loads only, no stores) the anchor is the *first* load per location — +/// treating that load as a free "memcpy" — and subsequent loads measure distance from it. +/// +/// Both the store and load index lists are merged in global order during seeking. +/// Backward seeks reset and replay from zero. +pub struct ReuseDistanceState { + geom: FuncGeometry, + is_input: bool, + /// Per `(x, y, channel)` anchor, flat row-major: `anchor_at[(y * width + x) * channels + c]`. + /// For intermediate Funcs: global index of the most recent store (`usize::MAX` = none yet). + /// For inputs: global index of the first load (`usize::MAX` = none yet). + anchor_at: Vec, + /// Maximum observed reuse distance per spatial pixel, indexed by `y * width + x`. + max_reuse_distance: Vec, + local_max_reuse_distance: u64, + global_max_reuse_distance: u64, + applied_store_k: usize, + applied_load_k: usize, + values: Vec, +} + +impl ReuseDistanceState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let n_cells = geom.width * geom.height * geom.channels; + let is_input = trace.func_store_indices(func).is_none_or(|s| s.is_empty()); + + let local_max_reuse_distance = trace.funcs.get(func).map(|s| s.max_reuse_distance)?; + let global_max_reuse_distance = trace.funcs.values().map(|s| s.max_reuse_distance).max()?; + + Some(Self { + geom, + is_input, + anchor_at: vec![usize::MAX; n_cells], + max_reuse_distance: vec![0u64; geom.width * geom.height], + local_max_reuse_distance, + global_max_reuse_distance, + applied_store_k: 0, + applied_load_k: 0, + values: vec![0f64; n_cells], + }) + } + + fn reset(&mut self) { + self.anchor_at.iter_mut().for_each(|v| *v = usize::MAX); + self.max_reuse_distance.iter_mut().for_each(|d| *d = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_store_k = 0; + self.applied_load_k = 0; + } + + /// Seeks to the state after the first `target_store_k` stores and `target_load_k` loads. + /// Events are replayed in global packet order via a two-pointer merge of the two sorted index + /// lists. Backward seeks (either counter regresses) reset and replay from zero. + pub fn seek( + &mut self, + trace: &Trace, + store_indices: &[usize], + load_indices: &[usize], + target_store_k: usize, + target_load_k: usize, + ) { + let target_store_k = target_store_k.min(store_indices.len()); + let target_load_k = target_load_k.min(load_indices.len()); + + if target_store_k < self.applied_store_k || target_load_k < self.applied_load_k { + self.reset(); + } + + let store_slice = &store_indices[self.applied_store_k..target_store_k]; + let load_slice = &load_indices[self.applied_load_k..target_load_k]; + let mut si = 0; + let mut li = 0; + + while si < store_slice.len() || li < load_slice.len() { + let next_is_store = si < store_slice.len() + && (li >= load_slice.len() || store_slice[si] < load_slice[li]); + + if next_is_store { + self.apply_store(&trace.packets[store_slice[si]], store_slice[si]); + si += 1; + } else { + self.apply_load(&trace.packets[load_slice[li]], load_slice[li]); + li += 1; + } + } + + self.applied_store_k = target_store_k; + self.applied_load_k = target_load_k; + } + + fn apply_store(&mut self, pkt: &TracePacket, global_idx: usize) { + // Pipeline inputs have no stores; skip to avoid a stale anchor being set. + if self.is_input { + return; + } + + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, _pixel_idx, val_idx| { + self.anchor_at[val_idx] = global_idx; + + if let Some(v) = pkt.decoded_value(lane) { + self.values[val_idx] = v; + } + }, + ); + } + + fn apply_load(&mut self, pkt: &TracePacket, global_idx: usize) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |_lane, pixel_idx, val_idx| { + if self.is_input { + // First load is the free memcpy; establish the anchor and record no distance. + // Subsequent loads to the same location measure from that first load. + if self.anchor_at[val_idx] == usize::MAX { + self.anchor_at[val_idx] = global_idx; + } else { + let dist = (global_idx - self.anchor_at[val_idx]) as u64; + self.max_reuse_distance[pixel_idx] = + self.max_reuse_distance[pixel_idx].max(dist); + } + } else if self.anchor_at[val_idx] != usize::MAX { + let dist = (global_idx - self.anchor_at[val_idx]) as u64; + self.max_reuse_distance[pixel_idx] = + self.max_reuse_distance[pixel_idx].max(dist); + } + }, + ); + } + + pub fn to_rgba(&self, normalization_mode: NormalizationMode) -> Vec { + let FuncGeometry { width, height, .. } = self.geom; + + let lut = Colormap::from_hex(&METRIC_PALETTE).to_lut(); + + let scale = match (normalization_mode, self.global_max_reuse_distance) { + (NormalizationMode::AcrossFuncs, 0) => 0.0, + (NormalizationMode::AcrossFuncs, global_max) => 255.0 / global_max as f64, + (NormalizationMode::PerFunc, _) => { + if self.local_max_reuse_distance > 0 { + 255.0 / self.local_max_reuse_distance as f64 + } else { + 0.0 + } + } + }; + + let mut out = vec![0u8; width * height * 4]; + for (chunk, &dist) in out.chunks_exact_mut(4).zip(self.max_reuse_distance.iter()) { + if dist > 0 { + let ti = (dist as f64 * scale) as usize; + let [r, g, b] = lut[ti.min(255)]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + } + chunk[3] = 255; + } + out + } + + pub fn to_tabular_data(&self, normalization_mode: NormalizationMode) -> Vec { + let max = match normalization_mode { + NormalizationMode::AcrossFuncs => self.global_max_reuse_distance, + NormalizationMode::PerFunc => self.local_max_reuse_distance, + }; + + let mut tabular_data = vec![0u32; 64]; + if max > 0 { + for &dist in &self.max_reuse_distance { + if dist > 0 { + let bucket = ((dist as f64 / max as f64) * 63.0) as usize; + tabular_data[bucket.min(63)] += 1; + } + } + } + + tabular_data + } + + pub fn to_values(&self) -> Vec { + self.max_reuse_distance.clone() + } + + pub fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + pub fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} + +// ── Thread Rendering ───────────────────────────────────────────────────────────────────────────── + +#[derive(Deserialize, Clone, Copy, PartialEq)] +pub enum ThreadOpMode { + Store, + Load, +} + +pub struct ThreadState { + geom: FuncGeometry, + thread_ids: Vec, + thread_id_buffer: Vec, + global_thread_ids: Vec, + store_counts: Vec, + load_counts: Vec, + applied_store_k: usize, + applied_load_k: usize, + applied_op_mode: Option, + values: Vec, +} + +impl ThreadState { + pub fn new(trace: &Trace, func: &str) -> Option { + let geom = trace.func_geometry(func)?; + let thread_ids = trace + .func_thread_ids(func) + .map(|ids| ids.iter().copied().collect::>()) + .unwrap_or_else(|| vec![0]); + let n_threads = thread_ids.len(); + + let global_thread_ids: Vec = trace.global_thread_ids.iter().copied().collect(); + + // `-1` marks a pixel no store/load has touched yet. + let thread_id_buffer = vec![-1; geom.width * geom.height]; + + Some(Self { + geom, + thread_ids, + thread_id_buffer, + global_thread_ids, + store_counts: vec![0u32; n_threads], + load_counts: vec![0u32; n_threads], + applied_store_k: 0, + applied_load_k: 0, + applied_op_mode: None, + values: vec![0f64; geom.width * geom.height * geom.channels], + }) + } + + fn reset(&mut self) { + self.thread_id_buffer.iter_mut().for_each(|v| *v = -1); + self.store_counts.iter_mut().for_each(|c| *c = 0); + self.load_counts.iter_mut().for_each(|c| *c = 0); + self.values.iter_mut().for_each(|v| *v = 0.0); + self.applied_store_k = 0; + self.applied_load_k = 0; + } + + fn apply_store(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + let thread_idx = self.thread_ids.binary_search(&pkt.thread_id).ok(); + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, pixel_idx: usize, val_idx: usize| { + if let Some(v) = pkt.decoded_value(lane) { + self.values[val_idx] = v; + }; + + self.thread_id_buffer[pixel_idx] = pkt.thread_id; + if let Some(i) = thread_idx { + self.store_counts[i] += 1; + } + }, + ); + } + + fn apply_load(&mut self, pkt: &TracePacket) { + let FuncGeometry { + width, + height, + channels, + min_x, + min_y, + min_c, + .. + } = self.geom; + let thread_idx = self.thread_ids.binary_search(&pkt.thread_id).ok(); + + for_each_lane_pixel( + pkt, + min_x, + min_y, + width, + height, + Some((min_c, channels)), + |lane, pixel_idx: usize, val_idx: usize| { + if let Some(v) = pkt.decoded_value(lane) { + self.values[val_idx] = v; + }; + + self.thread_id_buffer[pixel_idx] = pkt.thread_id; + if let Some(i) = thread_idx { + self.load_counts[i] += 1; + } + }, + ); + } + + pub fn seek( + &mut self, + trace: &Trace, + store_indices: &[usize], + load_indices: &[usize], + target_store_k: usize, + target_load_k: usize, + op_mode: ThreadOpMode, + ) { + let target_store_k = target_store_k.min(store_indices.len()); + let target_load_k = target_load_k.min(load_indices.len()); + + if target_store_k < self.applied_store_k + || target_load_k < self.applied_load_k + || self.applied_op_mode != Some(op_mode) + { + self.reset(); + } + + let store_slice = &store_indices[self.applied_store_k..target_store_k]; + let load_slice = &load_indices[self.applied_load_k..target_load_k]; + let mut si = 0; + let mut li = 0; + + while si < store_slice.len() || li < load_slice.len() { + let next_is_store = si < store_slice.len() + && (li >= load_slice.len() || store_slice[si] < load_slice[li]); + + match (&op_mode, next_is_store) { + (ThreadOpMode::Store, true) => { + let pkt = &trace.packets[store_slice[si]]; + self.apply_store(pkt); + si += 1; + } + (ThreadOpMode::Load, false) => { + let pkt = &trace.packets[load_slice[li]]; + self.apply_load(pkt); + li += 1; + } + (_, true) => { + // Increment si even if we don't apply the store, to keep the merge moving forward. + si += 1; + } + (_, false) => { + // Increment li even if we don't apply the load, to keep the merge moving forward. + li += 1; + } + } + } + + self.applied_store_k = target_store_k; + self.applied_load_k = target_load_k; + self.applied_op_mode = Some(op_mode); + } + + pub fn to_rgba(&self, thread_id_filter: String) -> Vec { + let FuncGeometry { width, height, .. } = self.geom; + let mut out = vec![0u8; width * height * 4]; + let filter_id: Option = thread_id_filter.parse::().ok(); + + for (chunk, &thread_id) in out.chunks_exact_mut(4).zip(self.thread_id_buffer.iter()) { + let color = self + .global_thread_ids + .binary_search(&thread_id) + .ok() + .filter(|&rank| rank < colorous::SET3.len()) + .map(|rank| colorous::SET3[rank]); + + if let Some(color) = color { + chunk[0] = color.r; + chunk[1] = color.g; + chunk[2] = color.b; + chunk[3] = if filter_id == Some(thread_id) || filter_id == Some(-1) { + 255 + } else { + 64 + }; + } else { + chunk[0] = 0; + chunk[1] = 0; + chunk[2] = 0; + chunk[3] = 255; + } + } + + out + } + + pub fn to_values(&self) -> Vec { + self.thread_id_buffer.clone() + } + + pub fn to_thread_counts(&self) -> (&[u32], &[u32]) { + (&self.store_counts, &self.load_counts) + } + + pub fn to_nan_overlay(&self) -> Vec { + nan_mask(&self.values, self.geom.channels) + } + + pub fn to_inf_overlay(&self) -> Vec { + inf_mask(&self.values, self.geom.channels) + } +} diff --git a/tools/halidoscope/src-tauri/src/trace.rs b/tools/halidoscope/src-tauri/src/trace.rs new file mode 100644 index 000000000000..e625c9a5c65c --- /dev/null +++ b/tools/halidoscope/src-tauri/src/trace.rs @@ -0,0 +1,1150 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +// ── Type system ────────────────────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TypeCode { + Int, + Uint, + Float, + Handle, + BFloat, + Unknown(u8), +} + +impl TypeCode { + fn from_u8(v: u8) -> Self { + match v { + 0 => Self::Int, + 1 => Self::Uint, + 2 => Self::Float, + 3 => Self::Handle, + 4 => Self::BFloat, + other => Self::Unknown(other), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HalideType { + pub code: TypeCode, + pub bits: u8, + pub lanes: u16, +} + +impl HalideType { + // Obtain the number of bytes for a single scalar element (i.e., one SIMD lane) of a packet's + // value. For sub-byte types, this rounds up to the nearest whole byte. + pub fn elem_bytes(self) -> usize { + (self.bits as usize).div_ceil(8) + } + + // Obtain the number of bytes for the entire value of a packet. This is the product of the + // number of lanes and the size of each lane. + pub fn value_bytes(self) -> usize { + self.lanes as usize * self.elem_bytes() + } +} + +// ── Event codes ────────────────────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EventCode { + Load, + Store, + BeginRealization, + EndRealization, + Produce, + EndProduce, + Consume, + EndConsume, + BeginPipeline, + EndPipeline, + Tag, + BeginParallelTask, + EndParallelTask, + Unknown(i32), +} + +impl EventCode { + fn from_i32(v: i32) -> Self { + match v { + 0 => Self::Load, + 1 => Self::Store, + 2 => Self::BeginRealization, + 3 => Self::EndRealization, + 4 => Self::Produce, + 5 => Self::EndProduce, + 6 => Self::Consume, + 7 => Self::EndConsume, + 8 => Self::BeginPipeline, + 9 => Self::EndPipeline, + 10 => Self::Tag, + 11 => Self::BeginParallelTask, + 12 => Self::EndParallelTask, + other => Self::Unknown(other), + } + } +} + +// ── Parsed packet ───────────────────────────────────────────────────────────── + +#[derive(Debug, Clone)] +pub struct TracePacket { + /// This packet's own id, for the purpose of other packets' `parent_id`. Only meaningful for + /// non-load/store events; load/store packets are leaves (nothing parents against them) and + /// carry `value_index` in this slot instead, so `id` is meaningless for them. + pub id: i32, + pub event: EventCode, + pub parent_id: i32, + /// Which tuple element was accessed. Only meaningful for load/store events. + pub value_index: i32, + /// Only meaningful for load/store events. + pub type_: HalideType, + /// The Halide-internal thread that executed this event. Parsed directly off + /// `BeginParallelTask` packets; for Load/Store packets (whose header reuses this byte offset + /// for `type_` in the raw binary format) it's resolved after parsing by walking the parent + /// chain up to the nearest enclosing `BeginParallelTask` (0 if there is none, i.e. serial + /// execution). Meaningless for every other event. + pub thread_id: i32, + /// Coordinates in dim-major / lane-minor order: [x₀..xₙ, y₀..yₙ, c₀..cₙ] where n = type_.lanes. + pub coordinates: Vec, + pub value: Vec, + pub func: String, + pub trace_tag: String, +} + +impl TracePacket { + pub fn is_load(&self) -> bool { + self.event == EventCode::Load + } + + pub fn is_store(&self) -> bool { + self.event == EventCode::Store + } + + pub fn is_load_or_store(&self) -> bool { + self.is_load() || self.is_store() + } + + /// Decodes lane `lane` of this packet's value into an `f64`. Returns `None` when the type isn't + /// a decodable numeric (Handle / Unknown / an odd bit width) or when the lane runs past the + /// value bytes. All numeric types collapse to `f64` so callers have a single comparable scalar. + pub fn decoded_value(&self, lane: usize) -> Option { + let elem_bytes = self.type_.elem_bytes(); + if elem_bytes == 0 { + return None; + } + + let off = lane * elem_bytes; + if off + elem_bytes > self.value.len() { + return None; + } + + let s = &self.value[off..]; + match (self.type_.code, self.type_.bits) { + (TypeCode::Float, 32) => Some(f32::from_le_bytes(s[..4].try_into().unwrap()) as f64), + (TypeCode::Float, 64) => Some(f64::from_le_bytes(s[..8].try_into().unwrap())), + (TypeCode::Int, 8) => Some(s[0] as i8 as f64), + (TypeCode::Int, 16) => Some(i16::from_le_bytes(s[..2].try_into().unwrap()) as f64), + (TypeCode::Int, 32) => Some(i32::from_le_bytes(s[..4].try_into().unwrap()) as f64), + (TypeCode::Int, 64) => Some(i64::from_le_bytes(s[..8].try_into().unwrap()) as f64), + (TypeCode::Uint, 8) => Some(s[0] as f64), + (TypeCode::Uint, 16) => Some(u16::from_le_bytes(s[..2].try_into().unwrap()) as f64), + (TypeCode::Uint, 32) => Some(u32::from_le_bytes(s[..4].try_into().unwrap()) as f64), + (TypeCode::Uint, 64) => Some(u64::from_le_bytes(s[..8].try_into().unwrap()) as f64), + // bfloat16 is the upper 16 bits of an IEEE f32; reconstruct by shifting left 16. + (TypeCode::BFloat, 16) => { + let bits = u16::from_le_bytes(s[..2].try_into().unwrap()); + Some(f32::from_bits((bits as u32) << 16) as f64) + } + _ => None, + } + } +} + +// ── Per-Func statistics ─────────────────────────────────────────────────────── + +#[derive(Debug, Clone, Default)] +pub struct FuncStats { + pub name: String, + pub min_coords: Vec, + pub max_coords: Vec, + pub min_value: Option, + pub max_value: Option, + /// Maximum number of stores observed at any array / tensor coordinate for this Func. + pub max_store_count: u32, + /// Maximum number of loads observed at any array / tensor coordinate for this Func. + pub max_load_count: u32, + /// Maximum number of redundant stores observed at any array / tensor coordinate for this Func. + /// A store is considered redundant when the incoming value bit-matches the previously stored + /// value at that location AND there are no intervening loads from that location. + pub max_redundant_store_count: u32, + /// Maximum store-to-load distance observed across all array / tensor coordinates for this Func. + /// Measured as the difference in global packet indices between a store and the next load from + /// the same coordination. + pub max_reuse_distance: u64, +} + +/// Full spatial layout of a Func: pixel dimensions plus the channel axis (logical dim 2). +#[derive(Debug, Clone, Copy)] +pub struct FuncGeometry { + pub width: usize, + pub height: usize, + pub channels: usize, + pub min_x: i32, + pub min_y: i32, + pub min_c: i32, + pub max_store_count: u32, + pub max_load_count: u32, + pub max_redundant_store_count: u32, + pub max_reuse_distance: u64, +} + +// ── Complete trace ─────────────────────────────────────────────────────────────────────────────── + +// Note: We use BTreeMaps for deterministic iteration order here. We could consider switching to +// HashMaps to get O(1) lookups if we find Func lookup starts to become a bottleneck. +pub struct Trace { + pub packets: Vec, + pub funcs: BTreeMap, + pub dag_edges: BTreeMap>, + pub store_indices_by_func: BTreeMap>, + pub load_indices_by_func: BTreeMap>, + pub buffer_liveness_range_by_func: BTreeMap, + pub produce_ranges_by_func: BTreeMap>, + pub consume_ranges_by_func: BTreeMap>, + pub thread_ids_by_func: BTreeMap>, + pub global_max_store_count: u32, + pub global_max_load_count: u32, + pub global_max_redundant_store_count: u32, + pub global_max_reuse_distance: u64, + pub global_thread_ids: BTreeSet, +} + +// ── Binary parsing helpers ─────────────────────────────────────────────────────────────────────── + +// Layout for `halide_trace_packet_t`'s fixed header, derived directly from HalideRuntime.h by +// bindgen (see build.rs) rather than hand-copied, so a layout change upstream is caught at compile +// time instead of silently desyncing. This only derives the struct's data layout: none of +// `halide_trace_packet_t`'s C++ accessor methods are bound, so the variable-length trailing data +// below is still walked by hand. +// +// Immediately after the header: +// i32 coordinates[dimensions] +// u8 value[type.lanes * ceil(type.bits / 8)] +// char func[] (null-terminated) +// char trace_tag[] (null-terminated; empty string if absent) +mod ffi { + #![allow(non_camel_case_types, non_upper_case_globals, dead_code)] + include!(concat!(env!("OUT_DIR"), "/halide_trace_bindings.rs")); +} +use ffi::halide_trace_packet_t; + +const HEADER_BYTES: usize = std::mem::size_of::(); + +// Helper function to read a little-endian i32 out of a byte buffer at a given offset. try_into() +// converts the slice to a fixed-size [u8; 4] array. Inlined for performance since it's called in +// the hot path of packet parsing (coordinate arrays). +#[inline] +fn i32_le(buf: &[u8], off: usize) -> i32 { + i32::from_le_bytes(buf[off..off + 4].try_into().unwrap()) +} + +/// Read a null-terminated C string. Returns `(string, bytes_consumed_including_null)`. +fn read_cstr(buf: &[u8]) -> (&str, usize) { + let null_idx = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + + ( + std::str::from_utf8(&buf[..null_idx]).unwrap_or(""), + null_idx + 1, + ) +} + +// ── Stats helpers called during packet parsing ──────────────────────────────── + +// Update the min/max coordinate vectors in FuncStats based on the coordinates seen in a given +// packet. The returned coordinates represent the range [min, max). +fn update_coord_range(pkt: &TracePacket, stats: &mut FuncStats) { + if pkt.coordinates.is_empty() { + return; + } + + let n_lanes = pkt.type_.lanes.max(1) as usize; + let logical_dims = pkt.coordinates.len() / n_lanes; + + // If this is the first load/store for this Func, initialize the min/max coordinate vectors. + // Otherwise, update the existing min/max values. + if stats.min_coords.is_empty() { + stats.min_coords.resize(logical_dims, 0); + stats.max_coords.resize(logical_dims, 0); + + for d in 0..logical_dims { + let mut mn = pkt.coordinates[d * n_lanes]; + let mut mx = mn + 1; + + for l in 1..n_lanes { + let coord = pkt.coordinates[d * n_lanes + l]; + mn = mn.min(coord); + mx = mx.max(coord + 1); + } + + stats.min_coords[d] = mn; + stats.max_coords[d] = mx; + } + } else { + for d in 0..logical_dims { + for l in 0..n_lanes { + let coord = pkt.coordinates[d * n_lanes + l]; + + stats.min_coords[d] = stats.min_coords[d].min(coord); + stats.max_coords[d] = stats.max_coords[d].max(coord + 1); + } + } + } +} + +// Update the min/max value in FuncStats based on the value seen in a given packet. +fn update_value_range(pkt: &TracePacket, stats: &mut FuncStats) { + for i in 0..pkt.type_.lanes as usize { + if let Some(v) = pkt.decoded_value(i) { + match (stats.min_value, stats.max_value) { + (None, _) => { + // Ensure we do not initialize min/max with NaN or Inf. + if v.is_nan() || v.is_infinite() { + continue; + } + + stats.min_value = Some(v); + stats.max_value = Some(v); + } + (Some(mn), Some(mx)) => { + if v < mn && v.is_finite() { + stats.min_value = Some(v); + } + + if v > mx && v.is_finite() { + stats.max_value = Some(v); + } + } + _ => {} + } + } + } +} + +// ── func_type_and_dim tag parsing ───────────────────────────────────────────── + +fn parse_func_type_and_dim( + func_name: &str, + trace_tag: &str, + funcs: &mut BTreeMap, +) { + // Format: "func_type_and_dim: [code bits lanes]{num_types} + // [min extent]{num_dims}" + let mut tokens = trace_tag.split_whitespace(); + tokens.next(); // consume "func_type_and_dim:" + + // Skip over the type descriptions, which we don't currently use. We could consider using them + // in the future to populate FuncStats.type if that'd be a useful addition. + let num_types: usize = match tokens.next().and_then(|s| s.parse().ok()) { + Some(n) => n, + None => return, + }; + + for _ in 0..num_types * 3 { + tokens.next(); + } + + // Parse the dimension descriptions to extract the overall min and max coordinates for the Func. + let num_dims: usize = match tokens.next().and_then(|s| s.parse().ok()) { + Some(n) => n, + None => return, + }; + + // Pre-allocate the min/max coordinate vectors to avoid repeated reallocations during parsing. + let mut min_coords = Vec::with_capacity(num_dims); + let mut max_coords = Vec::with_capacity(num_dims); + + for _ in 0..num_dims { + let min: i32 = match tokens.next().and_then(|s| s.parse().ok()) { + Some(v) => v, + None => break, + }; + let extent: i32 = match tokens.next().and_then(|s| s.parse().ok()) { + Some(v) => v, + None => break, + }; + + min_coords.push(min); + max_coords.push(min + extent); + } + + // Assign the declared extents wholesale, overwriting anything observed so far. + // Coordinate extents come from two sources that both write min_coords/max_coords: this tag + // (declared realization bounds) and update_coord_range (coords observed on Load/Store). + // + // In practice Halide emits this tag at pipeline start, before any load/store, so the common + // path is "tag seeds, accesses expand." + if !min_coords.is_empty() { + let entry = funcs.entry(func_name.to_owned()).or_default(); + entry.name = func_name.to_owned(); + entry.min_coords = min_coords; + entry.max_coords = max_coords; + } +} + +// ── Trace loading ──────────────────────────────────────────────────────────────────────────────── + +impl Trace { + pub fn load_from_file(path: &str, on_progress: impl FnMut(String, u8)) -> Result { + let data = std::fs::read(path).map_err(|e| e.to_string())?; + Self::load_from_bytes(&data, on_progress) + } + + pub fn load_from_bytes( + data: &[u8], + mut on_progress: impl FnMut(String, u8), + ) -> Result { + let total = data.len(); + let mut pos = 0; + let mut last_reported_pct: u8 = 0; + + let mut packets: Vec = Vec::new(); + let mut funcs: BTreeMap = BTreeMap::new(); + let mut dag_edges: BTreeMap> = BTreeMap::new(); + let mut store_indices_by_func: BTreeMap> = BTreeMap::new(); + let mut load_indices_by_func: BTreeMap> = BTreeMap::new(); + let mut buffer_liveness_range_by_func: BTreeMap = BTreeMap::new(); + let mut produce_ranges_by_func: BTreeMap> = BTreeMap::new(); + let mut consume_ranges_by_func: BTreeMap> = BTreeMap::new(); + let mut thread_ids_by_func: BTreeMap> = BTreeMap::new(); + + // Trace-level maximum values. + let mut global_max_store_count = 0u32; + let mut global_max_load_count = 0u32; + let mut global_max_redundant_store_count = 0u32; + let mut global_max_reuse_distance = 0u64; + let mut global_thread_ids: BTreeSet = BTreeSet::new(); + + // id -> (event, func_name, parent_id): needed for DAG inference after all packets are + // parsed. + let mut id_to_info: HashMap = HashMap::new(); + + // BeginParallelTask's own id -> the thread_id it carries. Load/Store packets don't carry + // a real thread_id (see TracePacket::thread_id), so resolving the thread that executed a + // given load/store requires walking its parent chain up to the nearest BeginParallelTask + // and looking up its thread_id here. + let mut thread_id_by_task_id: HashMap = HashMap::new(); + + // Loads we deferred for DAG inference. + let mut pending_loads: Vec<(String, i32)> = Vec::new(); + + // Packet parsing loop. + while pos + HEADER_BYTES <= total { + // Don't reinterpret-cast `data`'s bytes directly as `&halide_trace_packet_t` — we + // cannot statically prove the buffer is 4-byte aligned (though it practice it always + // should be), and an unaligned reference is UB. `read_unaligned` copies the header out + // by value instead. + let header: halide_trace_packet_t = + unsafe { std::ptr::read_unaligned(data[pos..pos + HEADER_BYTES].as_ptr().cast()) }; + + let size = header.size as usize; + if size < HEADER_BYTES || pos + size > total { + break; + } + + // ── Fixed header fields ─────────────────────────────────────────── + let parent_id = header.parent_id; + let dimensions = header.dimensions as usize; + + let ev = EventCode::from_i32(header.event as i32); + let is_load_or_store = matches!(ev, EventCode::Load | EventCode::Store); + + // `__bindgen_anon_1` is `id` for non-load/store events and `value_index` for + // load/store events; `__bindgen_anon_2` is `type` for load/store events and + // `thread_id` (else 0) otherwise. See halide_trace_packet_t in HalideRuntime.h. + // Reading a union field is inherently unsafe — the type can't track which variant + // is valid, so it's on us to pick the right one based on `ev`, same as the raw + // offset reads this replaced. + let (id, value_index) = if is_load_or_store { + (0, unsafe { header.__bindgen_anon_1.value_index }) + } else { + (unsafe { header.__bindgen_anon_1.id }, 0) + }; + + let (type_, thread_id) = if is_load_or_store { + let inner = unsafe { header.__bindgen_anon_2.__bindgen_anon_1 }; + let type_ = HalideType { + code: TypeCode::from_u8(inner.type_code), + bits: inner.type_bits, + lanes: inner.lanes, + }; + + // Initialize thread_ids as a sentinel value of -1. + (type_, -1) + } else { + ( + HalideType { + code: TypeCode::from_u8(0), + bits: 0, + lanes: 0, + }, + unsafe { header.__bindgen_anon_2.thread_id }, + ) + }; + + let pkt_data = &data[pos..pos + size]; + + // ── Variable-length trailing fields ─────────────────────────────── + let coords_off = HEADER_BYTES; + let value_off = coords_off + dimensions * 4; + // Only load/store packets have a value; the type/thread_id slot isn't a real + // halide_type_t for other events, so value_bytes() must not be trusted for them. + let value_len = if is_load_or_store { + type_.value_bytes() + } else { + 0 + }; + let func_off = value_off + value_len; + + let coords: Vec = (0..dimensions) + .map(|i| i32_le(pkt_data, coords_off + i * 4)) + .collect(); + + let value = pkt_data + .get(value_off..value_off + value_len) + .map(|s| s.to_vec()) + .unwrap_or_default(); + + let (func_name, func_len) = pkt_data + .get(func_off..) + .map(|s| { + let (name, n) = read_cstr(s); + (name.to_owned(), n) + }) + .unwrap_or_default(); + + let tag_off = func_off + func_len; + let trace_tag = pkt_data + .get(tag_off..) + .map(|s| read_cstr(s).0.to_owned()) + .unwrap_or_default(); + + // ── Pipeline context propagation ───────────────────────────────────────────────────── + // Load/store packets don't carry a real `id` (that slot holds `value_index` + // instead) and are leaves that nothing ever parents against, so they're excluded + // here to avoid a `value_index` colliding with and clobbering a real packet's entry. + if !is_load_or_store { + id_to_info.insert(id, (ev, func_name.clone(), parent_id)); + } + + // ── Build the packet ───────────────────────────────────────────────────────────────── + let pkt = TracePacket { + id, + event: ev, + parent_id, + value_index, + type_, + thread_id, + coordinates: coords, + value, + func: func_name.clone(), + trace_tag: trace_tag.clone(), + }; + + // ── Update per-Func stats ───────────────────────────────────────── + // Coordinate extents are populated from two sources below: the func_type_and_dim tag + // (declared bounds) and Load/Store coords (observed bounds). Both write min_coords / + // max_coords; their interaction is order-dependent by design. + match ev { + EventCode::Tag if trace_tag.starts_with("func_type_and_dim:") => { + parse_func_type_and_dim(&func_name, &trace_tag, &mut funcs); + } + EventCode::BeginRealization => { + let entry = funcs.entry(func_name.clone()).or_default(); + entry.name = func_name.clone(); + + // Start the liveness range for this Func at the current packet index. + let idx = packets.len() as u32; + + buffer_liveness_range_by_func + .entry(func_name.clone()) + .and_modify(|range| range.0 = range.0.min(idx)) + .or_insert((idx, idx)); + } + EventCode::EndRealization => { + // End the liveness range for this Func at the current packet index. + let idx = packets.len() as u32; + + buffer_liveness_range_by_func + .entry(func_name.clone()) + .and_modify(|range| range.1 = range.1.max(idx)) + .or_insert((idx, idx)); + } + EventCode::Load => { + // When we observe a load event, add its current index (equivalent to + // packets.len() before the push) to our BTreeMap of load indices for this Func. + load_indices_by_func + .entry(func_name.clone()) + .or_default() + .push(packets.len()); + + // Add the load event to the list of pending loads to support DAG inference. + pending_loads.push((func_name.clone(), parent_id)); + let stats = funcs.entry(func_name.clone()).or_insert_with(|| FuncStats { + name: func_name.clone(), + ..Default::default() + }); + + // Update the min/max coordinate and value ranges for this Func based on the + // current load packet. + update_coord_range(&pkt, stats); + update_value_range(&pkt, stats); + } + EventCode::Store => { + // When we observe a store event, add its current index (equivalent to + // packets.len() before the push) to our BTreeMap of store indices for this + // Func. + store_indices_by_func + .entry(func_name.clone()) + .or_default() + .push(packets.len()); + + // Update the min/max coordinate and value ranges for this Func based on the + // current store packet. + let stats = funcs.entry(func_name.clone()).or_insert_with(|| FuncStats { + name: func_name.clone(), + ..Default::default() + }); + update_coord_range(&pkt, stats); + update_value_range(&pkt, stats); + } + EventCode::Produce => { + let idx = packets.len() as u32; + + // When we observe a Produce event for a Func A, this signals that A is + // consuming some other Func B. Thus, we store this as A's consume range. + consume_ranges_by_func + .entry(func_name.clone()) + .or_default() + .push((idx, idx)); + } + EventCode::EndProduce => { + let idx = packets.len() as u32; + + if let Some(ranges) = consume_ranges_by_func.get_mut(func_name.as_str()) { + if let Some(last) = ranges.last_mut() { + last.1 = idx; + } + } + } + EventCode::Consume => { + let idx = packets.len() as u32; + + // When we observe a Consume event for a Func A, this signals that A is + // producing for (being consumed by) some other Func B. Thus, we store this as + // A's produce range. + produce_ranges_by_func + .entry(func_name.clone()) + .or_default() + .push((idx, idx)); + } + EventCode::EndConsume => { + let idx = packets.len() as u32; + + if let Some(ranges) = produce_ranges_by_func.get_mut(func_name.as_str()) { + if let Some(last) = ranges.last_mut() { + last.1 = idx; + } + } + } + EventCode::BeginParallelTask => { + thread_id_by_task_id.insert(id, thread_id); + } + _ => {} + } + + packets.push(pkt); + pos += size; + + let pct = (pos as u64 * 100 / total.max(1) as u64) as u8; + if pct > last_reported_pct { + last_reported_pct = pct; + on_progress("Loading trace...".to_string(), pct); + } + } + + // ── DAG inference ──────────────────────────────────────────────────────────────────────── + // Walk up the parent chain from each load to find the enclosing Produce event; that + // Produce's func is a producer of the loaded func. This walk is O(pending_loads * chain + // depth) and pending_loads has one entry per Load packet, so it can take a while on large + // traces — report progress through it rather than sitting silent until it's done. + let total_pending_loads = pending_loads.len(); + let mut dag_last_reported_pct: u8 = 0; + for (index, (func_name, load_parent_id)) in pending_loads.iter().enumerate() { + let loaded_func = func_name.clone(); + + let mut current = *load_parent_id; + loop { + match id_to_info.get(¤t) { + Some((EventCode::Produce, producing_func, _)) => { + if loaded_func != *producing_func { + dag_edges + .entry(loaded_func.clone()) + .or_default() + .insert(producing_func.clone()); + } + break; + } + Some((_, _, next_parent)) => current = *next_parent, + None => break, + } + } + + let pct = (index as u64 * 25 / total_pending_loads.max(1) as u64) as u8; + if pct > dag_last_reported_pct { + dag_last_reported_pct = pct; + on_progress("Analyzing trace...".to_string(), pct); + } + } + + on_progress("Analyzing trace...".to_string(), 25); + + // Resolve the executing thread for each load/store packet by walking its parent chain up + // to the nearest enclosing `BeginParallelTask`. Exclude any stores / loads that do not + // have a meaningful thread_id (denoted by a sentinel value of -1). + let total_packets = packets.len(); + let mut thread_last_reported_pct: u8 = 25; + for (index, pkt) in packets.iter_mut().enumerate() { + let pct = 25 + (index as u64 * 25 / total_packets.max(1) as u64) as u8; + if pct > thread_last_reported_pct { + thread_last_reported_pct = pct; + on_progress("Analyzing trace...".to_string(), pct); + } + + if !pkt.is_load_or_store() { + continue; + } + + let mut current = pkt.parent_id; + loop { + if let Some(&tid) = thread_id_by_task_id.get(¤t) { + pkt.thread_id = tid; + break; + } + + match id_to_info.get(¤t) { + Some((_, _, next_parent)) => current = *next_parent, + None => break, + } + } + + if pkt.thread_id != -1 { + thread_ids_by_func + .entry(pkt.func.clone()) + .or_default() + .insert(pkt.thread_id); + + // Insert the thread id into the global_thread_ids BTreeSet. + global_thread_ids.insert(pkt.thread_id); + } + } + + // Compute max per-pixel store count, load count, redundant store count, and reuse + // distance for each Func. All four statistics are derived from a single two-pointer + // merge of that Func's store/load indices in global packet order, so we compute them + // together in one walk rather than re-merging the same indices four separate times. + // (Redundant store count: a store is redundant when the incoming value bit-matches the + // previously stored value at that location and there have been no intervening loads from + // that location. Reuse distance: the packet-index gap between a store and the next load + // from the same (x, y, channel).) + // + // We extract extents/channels first (shared borrow) then write back (mut borrow) to keep + // the two borrows of `funcs` non-overlapping. + let total_store_funcs = store_indices_by_func.len(); + let mut merge_last_reported_pct: u8 = 50; + for (index, (func_name, store_indices)) in store_indices_by_func.iter().enumerate() { + let extents = funcs.get(func_name.as_str()).and_then(func_extents); + if let Some((w, h, min_x, min_y)) = extents { + let stats = funcs.get(func_name.as_str()).unwrap(); + let (channels, min_c) = if stats.min_coords.len() >= 3 { + ( + (stats.max_coords[2] - stats.min_coords[2]).max(1) as usize, + stats.min_coords[2], + ) + } else { + (1, 0) + }; + + let load_indices = load_indices_by_func + .get(func_name.as_str()) + .map(Vec::as_slice) + .unwrap_or(&[]); + + let mut store_counts = vec![0u32; w * h]; + let mut load_counts = vec![0u32; w * h]; + // None = no store has landed here yet; Some(bits) = last stored value as u64 bits. + let mut last_values = vec![None::; w * h * channels]; + let mut redundant_counts = vec![0u32; w * h]; + // usize::MAX = no store has landed at this (x, y, channel) yet. + let mut last_store_at = vec![usize::MAX; w * h * channels]; + let mut max_reuse_distances = vec![0u64; w * h]; + let mut si = 0; + let mut li = 0; + + while si < store_indices.len() || li < load_indices.len() { + let next_is_store = si < store_indices.len() + && (li >= load_indices.len() || store_indices[si] < load_indices[li]); + + if next_is_store { + let global_idx = store_indices[si]; + si += 1; + + let pkt = &packets[global_idx]; + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + None, + |_lane, pixel_idx, _val_idx| { + store_counts[pixel_idx] += 1; + }, + ); + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + Some((min_c, channels)), + |lane, pixel_idx, val_idx| { + if let Some(v) = pkt.decoded_value(lane) { + let v_bits = v.to_bits(); + if let Some(prev_bits) = last_values[val_idx] { + if prev_bits == v_bits { + redundant_counts[pixel_idx] += 1; + } + } + last_values[val_idx] = Some(v_bits); + } + last_store_at[val_idx] = global_idx; + }, + ); + } else { + let global_idx = load_indices[li]; + li += 1; + + let pkt = &packets[global_idx]; + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + None, + |_lane, pixel_idx, _val_idx| { + load_counts[pixel_idx] += 1; + }, + ); + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + Some((min_c, channels)), + |_lane, pixel_idx, val_idx| { + // A load resets redundancy tracking for this location: an + // intervening load means the next store, even if bit-identical, + // is not redundant. + last_values[val_idx] = None; + + if last_store_at[val_idx] != usize::MAX { + let dist = (global_idx - last_store_at[val_idx]) as u64; + if dist > max_reuse_distances[pixel_idx] { + max_reuse_distances[pixel_idx] = dist; + } + } + }, + ); + } + } + + if let Some(stats) = funcs.get_mut(func_name.as_str()) { + stats.max_store_count = store_counts.iter().copied().max().unwrap_or(0); + stats.max_load_count = load_counts.iter().copied().max().unwrap_or(0); + stats.max_redundant_store_count = + redundant_counts.iter().copied().max().unwrap_or(0); + stats.max_reuse_distance = + max_reuse_distances.iter().copied().max().unwrap_or(0); + + global_max_store_count = global_max_store_count.max(stats.max_store_count); + global_max_load_count = global_max_load_count.max(stats.max_load_count); + global_max_redundant_store_count = + global_max_redundant_store_count.max(stats.max_redundant_store_count); + global_max_reuse_distance = + global_max_reuse_distance.max(stats.max_reuse_distance); + } + } + + let pct = 50 + (index as u64 * 25 / total_store_funcs.max(1) as u64) as u8; + if pct > merge_last_reported_pct { + merge_last_reported_pct = pct; + on_progress("Analyzing trace...".to_string(), pct); + } + } + + on_progress("Analyzing trace...".to_string(), 75); + + // Pipeline inputs: Funcs with loads but no stores. These aren't covered by the merged + // loop above, so compute their load count and reuse distance in one pass here. The first + // load at each (x, y, channel) is free (analogous to a memcpy); subsequent loads measure + // distance from that first load. + let total_load_funcs = load_indices_by_func.len(); + let mut load_funcs_last_reported_pct: u8 = 75; + for (index, (func_name, load_indices)) in load_indices_by_func.iter().enumerate() { + let pct = 75 + (index as u64 * 25 / total_load_funcs.max(1) as u64) as u8; + if pct > load_funcs_last_reported_pct { + load_funcs_last_reported_pct = pct; + on_progress("Analyzing trace...".to_string(), pct); + } + + if store_indices_by_func.contains_key(func_name.as_str()) { + continue; // handled by the merged loop above + } + let extents = funcs.get(func_name.as_str()).and_then(func_extents); + if let Some((w, h, min_x, min_y)) = extents { + let stats = funcs.get(func_name.as_str()).unwrap(); + let (channels, min_c) = if stats.min_coords.len() >= 3 { + ( + (stats.max_coords[2] - stats.min_coords[2]).max(1) as usize, + stats.min_coords[2], + ) + } else { + (1, 0) + }; + + let mut load_counts = vec![0u32; w * h]; + // usize::MAX = first load hasn't occurred at this (x, y, channel) yet. + let mut first_load_at = vec![usize::MAX; w * h * channels]; + let mut max_reuse_distances = vec![0u64; w * h]; + + for &global_idx in load_indices { + let pkt = &packets[global_idx]; + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + None, + |_lane, pixel_idx, _val_idx| { + load_counts[pixel_idx] += 1; + }, + ); + for_each_lane_pixel( + pkt, + min_x, + min_y, + w, + h, + Some((min_c, channels)), + |_lane, pixel_idx, val_idx| { + if first_load_at[val_idx] == usize::MAX { + first_load_at[val_idx] = global_idx; + } else { + let dist = (global_idx - first_load_at[val_idx]) as u64; + if dist > max_reuse_distances[pixel_idx] { + max_reuse_distances[pixel_idx] = dist; + } + } + }, + ); + } + + if let Some(stats) = funcs.get_mut(func_name.as_str()) { + stats.max_load_count = load_counts.iter().copied().max().unwrap_or(0); + stats.max_reuse_distance = + max_reuse_distances.iter().copied().max().unwrap_or(0); + + global_max_load_count = global_max_load_count.max(stats.max_load_count); + global_max_reuse_distance = + global_max_reuse_distance.max(stats.max_reuse_distance); + } + } + } + + on_progress("Analyzing trace...".to_string(), 100); + + Ok(Self { + packets, + funcs, + dag_edges, + store_indices_by_func, + load_indices_by_func, + buffer_liveness_range_by_func, + produce_ranges_by_func, + consume_ranges_by_func, + thread_ids_by_func, + global_max_store_count, + global_max_load_count, + global_max_redundant_store_count, + global_max_reuse_distance, + global_thread_ids, + }) + } + + // ── Render-path accessors ───────────────────────────────────────────────── + + /// Global packet indices of `func_name`'s store events, in ascending order. `None` if the Func + /// emitted no stores. Use `partition_point(|&p| p <= g)` on the returned slice to turn a global + /// timeline index `g` into the number of stores that have occurred by that point. + pub fn func_store_indices(&self, func_name: &str) -> Option<&[usize]> { + self.store_indices_by_func.get(func_name).map(Vec::as_slice) + } + + pub fn func_load_indices(&self, func_name: &str) -> Option<&[usize]> { + self.load_indices_by_func.get(func_name).map(Vec::as_slice) + } + + pub fn func_buffer_liveness_range(&self, func_name: &str) -> Option<&(u32, u32)> { + self.buffer_liveness_range_by_func.get(func_name) + } + + pub fn func_produce_ranges(&self, func_name: &str) -> Option<&[(u32, u32)]> { + self.produce_ranges_by_func + .get(func_name) + .map(Vec::as_slice) + } + + pub fn func_consume_ranges(&self, func_name: &str) -> Option<&[(u32, u32)]> { + self.consume_ranges_by_func + .get(func_name) + .map(Vec::as_slice) + } + + pub fn func_thread_ids(&self, func_name: &str) -> Option<&BTreeSet> { + self.thread_ids_by_func.get(func_name) + } + + /// Spatial layout for `func_name`, or `None` if it has no usable coordinate extent. Reuses + /// `func_extents` for pixel dims so the renderer and the metadata layer agree, and adds the + /// channel axis (logical dim 2). + pub fn func_geometry(&self, func_name: &str) -> Option { + let stats = self.funcs.get(func_name)?; + let (width, height, min_x, min_y) = func_extents(stats)?; + let (channels, min_c) = if stats.min_coords.len() >= 3 { + ( + (stats.max_coords[2] - stats.min_coords[2]).max(0) as usize, + stats.min_coords[2], + ) + } else { + (1, 0) + }; + Some(FuncGeometry { + width, + height, + channels: channels.max(1), + min_x, + min_y, + min_c, + max_store_count: stats.max_store_count, + max_load_count: stats.max_load_count, + max_redundant_store_count: stats.max_redundant_store_count, + max_reuse_distance: stats.max_reuse_distance, + }) + } +} + +// ── Shared geometry helpers ─────────────────────────────────────────────────── + +/// Returns `(width, height, min_x, min_y)` for a Func, or `None` if the stats +/// have no coordinate information or produce a zero-area extent. +pub fn func_extents(stats: &FuncStats) -> Option<(usize, usize, i32, i32)> { + if stats.min_coords.is_empty() || stats.max_coords.is_empty() { + return None; + } + let width = (stats.max_coords[0] - stats.min_coords[0]) as usize; + let height = if stats.min_coords.len() > 1 { + (stats.max_coords[1] - stats.min_coords[1]) as usize + } else { + 1 + }; + if width == 0 || height == 0 { + return None; + } + let min_x = stats.min_coords[0]; + let min_y = if stats.min_coords.len() > 1 { + stats.min_coords[1] + } else { + 0 + }; + Some((width, height, min_x, min_y)) +} + +/// Returns the `(x, y)` canvas pixel for lane `l` of `pkt`, relative to the +/// Func's origin. Caller must bounds-check before indexing the canvas. +#[inline] +pub(crate) fn pixel_xy( + pkt: &TracePacket, + lane: usize, + n_lanes: usize, + dims_per_lane: usize, + min_x: i32, + min_y: i32, +) -> (i32, i32) { + let x = pkt.coordinates[lane] - min_x; + let y = if dims_per_lane >= 2 { + pkt.coordinates[n_lanes + lane] - min_y + } else { + -min_y + }; + (x, y) +} + +/// Iterates over each lane of `pkt` that falls within the Func's `w x h` extents, invoking +/// `f(lane, pixel_idx, val_idx)`. `pixel_idx` is the flattened `y * w + x` location. +/// +/// When `channel` is `Some((min_c, channels))`, lanes are additionally filtered to those whose +/// channel coordinate falls within `0..channels`, and `val_idx` is the flattened +/// `pixel_idx * channels + c` location; otherwise `val_idx` is just `pixel_idx`. +pub fn for_each_lane_pixel( + pkt: &TracePacket, + min_x: i32, + min_y: i32, + w: usize, + h: usize, + channel: Option<(i32, usize)>, + mut f: impl FnMut(usize, usize, usize), +) { + let n_lanes = pkt.type_.lanes.max(1) as usize; + let dims_per_lane = pkt.coordinates.len() / n_lanes; + for lane in 0..n_lanes { + let (x, y) = pixel_xy(pkt, lane, n_lanes, dims_per_lane, min_x, min_y); + if x < 0 || y < 0 || x as usize >= w || y as usize >= h { + continue; + } + + let pixel_idx = y as usize * w + x as usize; + let val_idx = if let Some((min_c, channels)) = channel { + let c = if dims_per_lane >= 3 { + pkt.coordinates[2 * n_lanes + lane] - min_c + } else { + 0 + }; + + if c < 0 || c as usize >= channels { + continue; + } + + pixel_idx * channels + c as usize + } else { + pixel_idx + }; + + f(lane, pixel_idx, val_idx); + } +} diff --git a/tools/halidoscope/src-tauri/tauri.conf.json b/tools/halidoscope/src-tauri/tauri.conf.json new file mode 100644 index 000000000000..34025e8d645b --- /dev/null +++ b/tools/halidoscope/src-tauri/tauri.conf.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Halidoscope", + "version": "0.1.0", + "identifier": "com.halide.halidoscope", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "pnpm build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "create": false, + "title": "Halidoscope", + "width": 1512, + "height": 982 + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": false, + "targets": [], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + }, + "plugins": { + "cli": { + "args": [ + { + "name": "trace", + "short": "t", + "takesValue": true, + "description": "Path to .hltrace file to load on startup." + }, + { + "name": "profile", + "short": "p", + "takesValue": true, + "description": "Path to Halide profile JSON file to load on startup." + } + ], + "subcommands": { + "snapshot": { + "args": [ + { + "name": "trace", + "short": "t", + "takesValue": true, + "description": "Path to .hltrace file to load for snapshotting.", + "required": true + }, + { + "name": "func", + "short": "f", + "takesValue": true, + "description": "Name of the Func to snapshot.", + "required": true + }, + { + "name": "packet-index", + "short": "i", + "takesValue": true, + "description": "Global packet index to snapshot." + }, + { + "name": "mode", + "short": "m", + "takesValue": true, + "possibleValues": [ + "grayscale", + "rgb", + "store-frequency", + "load-frequency", + "redundant-stores", + "reuse-distance" + ], + "description": "Rendering mode for the snapshot." + }, + { + "name": "destination", + "index": 1, + "takesValue": true, + "description": "Path to write the output snapshot image.", + "required": true + } + ] + }, + "dot": { + "args": [ + { + "name": "trace", + "short": "t", + "takesValue": true, + "description": "Path to .hltrace file to analyze for pipeline graph structure.", + "required": true + }, + { + "name": "destination", + "index": 1, + "takesValue": true, + "description": "Path to write the output snapshot image." + } + ] + }, + "list": { + "args": [ + { + "name": "trace", + "short": "t", + "takesValue": true, + "description": "Path to .hltrace file to analyze for pipeline graph structure.", + "required": true + }, + { + "name": "json", + "takesValue": false, + "description": "Print Func names and dimensionality in JSON format." + } + ] + }, + "stats": { + "args": [ + { + "name": "trace", + "short": "t", + "takesValue": true, + "description": "Path to .hltrace file to analyze for Func statistics.", + "required": true + }, + { + "name": "func", + "short": "f", + "takesValue": true, + "description": "Name of the Func to print statistics for. If omitted, prints statistics for all Funcs." + }, + { + "name": "json", + "takesValue": false, + "description": "Print statistics in JSON format." + } + ] + } + } + } + } +} diff --git a/tools/halidoscope/src/App.css b/tools/halidoscope/src/App.css new file mode 100644 index 000000000000..6989d363b4d9 --- /dev/null +++ b/tools/halidoscope/src/App.css @@ -0,0 +1,111 @@ +@import "tailwindcss"; +@import "@xyflow/react/dist/style.css"; + +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + + --zoom-level: 1; +} + +@theme { + --color-ps-primary: oklch(0.4423 0 0); + --color-ps-secondary: oklch(0.2768 0 0); + --color-ps-titlebar: oklch(0.3791 0 0); + --color-ps-text-primary: oklch(0.8975 0 0); + --color-ps-text-secondary: oklch(0.7444 0 0); + --color-ps-border-primary: oklch(0.3407 0 0); + --color-ps-border-secondary: oklch(0.3979 0 0); + --color-ps-border-tertiary: oklch(0.4997 0 0); + --color-oxide-green: oklch(0.77 0.1919 163.7); + --color-oxide-yellow: oklch(0.837 0.14 75); + --color-oxide-blue: oklch(0.71 0.15 272); + --color-oxide-red: oklch(0.712 0.185 11.3); + --color-oxide-purple: oklch(0.74 0.175 305.4); + + --text-tiny: 0.625rem; + --text-tiny--line-height: 1.5; + + --animate-blink: blink 1s step-end infinite; + + @keyframes blink { + 50% { + opacity: 0; + } + } +} + +@layer base { + /* Color Picker */ + ::-webkit-color-swatch-wrapper { + @apply p-0; + } + + ::-webkit-color-swatch { + @apply rounded-sm border-0; + } + + ::-moz-color-swatch { + @apply border-0; + } + + ::-moz-focus-inner { + @apply border-0; + } + + ::-moz-focus-inner { + @apply p-0; + } + + /* Number Input */ + /* Chrome, Safari, Edge, Opera */ + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* Firefox */ + input[type="number"] { + -moz-appearance: textfield; + } +} + +@layer components { + @keyframes slideDown { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } + + @keyframes slideUp { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } + + .accordion-content[data-state="open"] { + animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); + } + + .accordion-content[data-state="closed"] { + animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); + } +} diff --git a/tools/halidoscope/src/App.tsx b/tools/halidoscope/src/App.tsx new file mode 100644 index 000000000000..4242854e95f5 --- /dev/null +++ b/tools/halidoscope/src/App.tsx @@ -0,0 +1,228 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import { getMatches } from "@tauri-apps/plugin-cli"; +import { useSetAtom } from "jotai"; +import { Tabs } from "radix-ui"; +import * as React from "react"; + +import Profile from "@/components/profile/Profile"; +import Trace from "@/components/trace/Trace"; +import TraceUpload from "@/components/trace/TraceUpload"; +import TraceLoading from "@/components/trace/TraceLoading"; +import { ProfileContextProvider } from "@/hooks/profile"; +import { TraceContextProvider } from "@/hooks/trace"; +import { funcAtom } from "@/state/func"; +import type { Profile as Pfile } from "@/types/profile"; +import type { FuncMeta, StatsMeta } from "@/types/trace"; +import { openProfile, openTrace } from "@/utils/api"; + +import "./App.css"; + +async function resolvePath(path: string) { + return path.startsWith("/") + ? path + : `${await invoke("get_cwd")}/${path}`; +} + +class TracePathError extends Error { + public readonly name: string; + + constructor(message: string) { + super(message); + + this.name = "TracePathError"; + Object.setPrototypeOf(this, TracePathError.prototype); + } +} + +enum TraceLoadingState { + Loading, + Loaded, + NeedsUpload, +} + +function App() { + // Loading state. + const [traceLoading, setTraceLoading] = React.useState<{ + state: TraceLoadingState; + message: string; + progress: number; + }>({ + state: TraceLoadingState.Loading, + message: "Loading trace...", + progress: 0, + }); + + // Trace state. + const [funcs, setFuncs] = React.useState>({}); + const [dagEdges, setDagEdges] = React.useState>({}); + const [packetCount, setPacketCount] = React.useState(0); + const [stats, setStats] = React.useState({ + global_max_store_count: 0, + global_max_load_count: 0, + global_max_redundant_store_count: 0, + global_max_reuse_distance: 0, + global_thread_ids: [], + }); + + // Profile state. + const [profile, setProfile] = React.useState(null); + + // GUI state. + const setActiveFunc = useSetAtom(funcAtom); + + const loadTrace = React.useCallback( + async (path: string) => { + setTraceLoading({ + state: TraceLoadingState.Loading, + message: "Loading trace...", + progress: 0, + }); + + const unlisten = await listen<{ progress: number; message: string }>( + "trace-load-progress", + (event) => { + setTraceLoading((prev) => ({ + ...prev, + progress: event.payload.progress, + message: event.payload.message, + })); + }, + ); + + try { + const { funcs, total_packets, dag_edges, stats } = + await openTrace(path); + + const byName: Record = {}; + for (const func of funcs) { + byName[func.name] = func; + } + + setFuncs(byName); + setDagEdges(dag_edges); + setPacketCount(total_packets); + setStats(stats); + setActiveFunc(funcs[0]?.name ?? ""); + } finally { + unlisten(); + setTraceLoading({ + state: TraceLoadingState.Loaded, + message: "Trace loaded...", + progress: 100, + }); + } + }, + [setActiveFunc], + ); + + React.useEffect(() => { + async function loadTraceFromCLI() { + try { + const matches = await getMatches(); + const tracePath = matches.args.trace?.value; + + if (typeof tracePath !== "string") { + throw new TracePathError( + `Unexpected value for trace path: ${tracePath}`, + ); + } + + const resolvedTracePath = await resolvePath(tracePath); + await loadTrace(resolvedTracePath); + } catch (err) { + if (err instanceof TracePathError) { + setTraceLoading((prev) => ({ + ...prev, + state: TraceLoadingState.NeedsUpload, + })); + } else { + console.error("Error loading trace: ", err); + } + } + } + + loadTraceFromCLI(); + }, [loadTrace]); + + React.useEffect(() => { + async function loadProfileFromCLI() { + const matches = await getMatches(); + const profilePath = matches.args.profile?.value; + + if (typeof profilePath !== "string" || !profilePath) { + return; + } + const resolvedProfilePath = await resolvePath(profilePath); + + try { + const { pipelines } = await openProfile(resolvedProfilePath); + setProfile({ pipelines }); + } catch (err) { + console.error("Error loading profile from CLI: ", err); + } + } + + loadProfileFromCLI(); + }, []); + + const renderTrace = React.useCallback(() => { + switch (traceLoading.state) { + case TraceLoadingState.Loading: + return ( + + ); + case TraceLoadingState.NeedsUpload: + return ; + case TraceLoadingState.Loaded: + return ; + } + }, [traceLoading, loadTrace]); + + return ( + + + + Trace + + {profile !== null ? ( + + Profile + + ) : null} + + + +
{renderTrace()}
+
+
+ {profile !== null ? ( + + +
+ +
+
+
+ ) : null} +
+ ); +} + +export default App; diff --git a/tools/halidoscope/src/components/profile/Profile.tsx b/tools/halidoscope/src/components/profile/Profile.tsx new file mode 100644 index 000000000000..700971d6b668 --- /dev/null +++ b/tools/halidoscope/src/components/profile/Profile.tsx @@ -0,0 +1,42 @@ +import AllocationChurnChart from "@/components/profile/charts/AllocationChurnChart"; +import MemoryTreemap from "@/components/profile/charts/MemoryTreemap"; +import RuntimeChart from "@/components/profile/charts/RuntimeChart"; +import ProfileOverview from "@/components/profile/panels/ProfileOverview"; +import ProfilePanel from "@/components/profile/panels/ProfilePanel"; +import ProfilerTable from "@/components/profile/panels/ProfileTable"; +import type { Profile } from "@/types/profile"; + +function Profile() { + return ( +
+
+
+ + + + + + +
+
+ + + + + + +
+
+ +
+ ); +} + +export default Profile; diff --git a/tools/halidoscope/src/components/profile/charts/AllocationChurnChart.tsx b/tools/halidoscope/src/components/profile/charts/AllocationChurnChart.tsx new file mode 100644 index 000000000000..1079b1af2899 --- /dev/null +++ b/tools/halidoscope/src/components/profile/charts/AllocationChurnChart.tsx @@ -0,0 +1,344 @@ +import { clsx } from "clsx"; +import * as d3 from "d3"; +import { Tooltip } from "radix-ui"; +import * as React from "react"; + +import { useProfileContext } from "@/hooks/profile"; +import { formatBytes } from "@/utils/formatters"; + +interface ByteStringProps { + value: number; + className?: string; +} + +function ByteString({ value, className }: ByteStringProps) { + const { value: byteValue, unit } = formatBytes(value); + + return ( +

+ {byteValue} + + + {unit} + +

+ ); +} + +interface Props { + dimensions: { width: number; height: number }; +} + +const MARGIN = { + top: 25, + right: 20, + left: 50, + bottom: 50, +}; + +function AllocationChurnChart({ dimensions }: Props) { + const { pipelines } = useProfileContext(); + + const { funcs, runs } = pipelines[0]; + + const data = React.useMemo(() => { + const k = 1024; + const bins = ["B", "KB", "MB", "GB"]; + + return funcs + .map((func) => { + const index = Math.floor(Math.log(func.memory_peak) / Math.log(k)); + const byteMagnitude = bins[index]; + + return { + id: func.name, + x: func.memory_peak, + y: func.num_allocs / runs, + r: func.memory_total / runs, + byteMagnitude, + }; + }) + .filter((d) => d.x > 0 && d.y > 0); + }, [funcs, runs]); + + const x = d3 + .scaleLog() + .domain(d3.extent(data, (d) => d.x) as [number, number]) + .nice() + .range([MARGIN.left, dimensions.width - MARGIN.right]); + + const y = d3 + .scaleLog() + .domain(d3.extent(data, (d) => d.y) as [number, number]) + .nice() + .range([dimensions.height - MARGIN.bottom, MARGIN.top]); + + const r = d3 + .scaleSqrt() + .domain(d3.extent(data, (d) => d.r) as [number, number]) + .range([1, 25]); + + const gxRef = React.useRef(null); + const gyRef = React.useRef(null); + + // No tick marks, labels rotated -45°: matches the original Plot config + // (tickSize: 0, tickRotate: -45). + React.useEffect(() => { + if (!gxRef.current) { + return; + } + + d3.select(gxRef.current) + .call(d3.axisBottom(x).tickSize(0)) + .call((g) => g.select(".domain").remove()) + .selectAll("text") + .attr("transform", "rotate(-45)") + .style("text-anchor", "end"); + }, [x]); + + // Full-width gridlines instead of a left baseline: matches the original + // Plot config (grid: true, ticks: 8). + React.useEffect(() => { + if (!gyRef.current) { + return; + } + + d3.select(gyRef.current) + .call( + d3 + .axisLeft(y) + .ticks(8) + .tickSize(-(dimensions.width - MARGIN.left - MARGIN.right)), + ) + .call((g) => g.select(".domain").remove()) + .call((g) => g.selectAll(".tick line").attr("stroke-opacity", 0.1)); + }, [y, dimensions.width]); + + const legend = React.useMemo(() => { + // Three representative values whose sqrt-scaled radii halve at each step, + // rendered smallest-to-largest left to right. + const max = data.reduce((acc, d) => Math.max(acc, d.r), 0); + const values = [max / 16, max / 4, max].filter((v) => v > 0); + if (values.length === 0) { + return null; + } + + const titleHeight = 18; + const labelHeight = 16; + const maxRadius = r(values[values.length - 1]); + const colWidth = 2 * maxRadius; + const baseline = titleHeight + 2 * maxRadius; + const height = baseline + labelHeight + 2; + // Right-align the columns against the SVG's right edge. + const offsetX = dimensions.width - MARGIN.right - values.length * colWidth; + + return ( + + + + Peak Memory + + (Byte Order of Mag.) + + + + B + + + + KB + + + + MB + + + + GB + + + + + Total Memory + + {values.map((value, index) => { + const radius = r(value); + const cx = offsetX + index * colWidth + colWidth / 2; + const cy = baseline - radius; + const { value: byteValue, unit } = formatBytes(value); + + return ( + + + + {`${byteValue}\u2009${unit}`} + + + ); + })} + + ); + }, [data, dimensions.width, r]); + + return ( + +
+ {legend} + + + + + Peak Memory → + + + ↑ Num Allocs + + + {data.map((d, i) => ( + + + + + + +
+

{d.id}

+
+

Peak Memory

+ +

Total Memory

+ +

Num Allocs

+

{d.y}

+
+
+ + + + + ))} + + +
+
+ ); +} + +export default AllocationChurnChart; diff --git a/tools/halidoscope/src/components/profile/charts/MemoryTreemap.tsx b/tools/halidoscope/src/components/profile/charts/MemoryTreemap.tsx new file mode 100644 index 000000000000..e5f08c8b6291 --- /dev/null +++ b/tools/halidoscope/src/components/profile/charts/MemoryTreemap.tsx @@ -0,0 +1,140 @@ +import { clsx } from "clsx"; +import * as d3 from "d3"; +import { Tooltip } from "radix-ui"; +import * as React from "react"; + +import { useProfileContext } from "@/hooks/profile"; +import { formatBytes } from "@/utils/formatters"; + +export type TreemapNode = { + name: string; + value?: number; + children?: TreemapNode[]; +}; + +interface Props { + dimensions: { + width: number; + height: number; + }; +} + +function Treemap({ dimensions }: Props) { + const { pipelines } = useProfileContext(); + const { funcs, name } = pipelines[0]; + + const hierarchy = React.useMemo(() => { + const bins: TreemapNode[] = [ + { name: "B", children: [] }, + { name: "KB", children: [] }, + { name: "MB", children: [] }, + { name: "GB", children: [] }, + ]; + const k = 1024; + + funcs.forEach((func) => { + const index = Math.floor(Math.log(func.memory_peak) / Math.log(k)); + + if (index >= 0 && index < bins.length) { + bins[index]?.children?.push({ + name: func.name, + value: func.memory_peak, + }); + } + }); + + return { + name: name, + children: bins, + }; + }, [funcs, name]); + + const root = React.useMemo(() => { + const treemapHierarchy = d3 + .hierarchy(hierarchy) + .sum((d) => d.value ?? 0) + .sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); + + const treemap = d3 + .treemap() + .tile(d3.treemapSquarify) + .size([dimensions.width, dimensions.height]) + .padding(3) + .round(true); + + return treemap(treemapHierarchy); + }, [hierarchy, dimensions]); + + return ( + + + {root.leaves().map((leaf, index) => { + const { value, unit } = formatBytes(leaf.data.value ?? 0); + + return ( + + + + + + {leaf.x1 - leaf.x0 > 25 && leaf.y1 - leaf.y0 > 25 ? ( + <> + + + + + + {leaf.data.name} + + + {`${value}\u2009${unit}`} + + + + ) : null} + + + +

{leaf.data.name}

+

{`${value}\u2009${unit}`}

+ + + + + ); + })} + +
+ ); +} + +export default Treemap; diff --git a/tools/halidoscope/src/components/profile/charts/RuntimeChart.tsx b/tools/halidoscope/src/components/profile/charts/RuntimeChart.tsx new file mode 100644 index 000000000000..783efbae635a --- /dev/null +++ b/tools/halidoscope/src/components/profile/charts/RuntimeChart.tsx @@ -0,0 +1,167 @@ +import { clsx } from "clsx"; +import * as d3 from "d3"; +import { Tooltip } from "radix-ui"; +import * as React from "react"; + +import { useProfileContext } from "@/hooks/profile"; +import { ProfilePipeline, type ProfileFunc } from "@/types/profile"; + +interface SegmentLabelProps { + billed_runs: ProfilePipeline["billed_runs"]; + kind: ProfileFunc["kind"]; + name: ProfileFunc["name"]; + sampledTotal: number; + value: number; +} + +function SegmentLabel({ + billed_runs, + kind, + name, + sampledTotal, + value, +}: SegmentLabelProps) { + return ( +
+ {name} + + + {(value * 1e-6).toFixed(2)} ms + {" "} + ({((value / billed_runs / sampledTotal) * 100).toFixed(1)} + %) + +
+ ); +} + +interface Props { + dimensions: { + width: number; + height: number; + }; +} + +const PADDING = 16; +const BAR_HEIGHT = 64; +const LABEL_THRESHOLD = 80; + +function RuntimeChart({ dimensions }: Props) { + const { pipelines } = useProfileContext(); + + const { funcs, billed_runs } = pipelines[0]; + + const data = React.useMemo(() => { + return funcs + .filter((func) => func.time_ns > 0) + .map((func) => ({ + name: func.name, + kind: func.kind, + value: func.time_ns / billed_runs, + })); + }, [billed_runs, funcs]); + + const segments = React.useMemo(() => { + const sorted = [...data].sort((a, b) => b.value - a.value); + const total = d3.sum(sorted, (d) => d.value)!; + + const x = d3 + .scaleLinear() + .domain([0, total]) + .range([0, dimensions.width - 2 * PADDING]); + + // Inclusive prefix sums, so each segment starts where the previous ended. + const ends = d3.cumsum(sorted, (d) => d.value); + + return sorted.map((datum, index) => { + const start = x(ends[index] - datum.value); + + return { datum, x: start, width: x(ends[index]) - start }; + }); + }, [data, dimensions.width]); + + const sampledTotal = React.useMemo( + () => data.reduce((acc, datum) => acc + datum.value / billed_runs, 0), + [billed_runs, data], + ); + + return ( + + + + {segments.map(({ datum, x, width }) => ( + + + + + + + + + + + + ))} + {segments.map(({ datum, x, width }) => + width > LABEL_THRESHOLD ? ( + +
+ +
+
+ ) : null, + )} +
+
+
+ ); +} + +export default RuntimeChart; diff --git a/tools/halidoscope/src/components/profile/panels/ProfileOverview.tsx b/tools/halidoscope/src/components/profile/panels/ProfileOverview.tsx new file mode 100644 index 000000000000..912711950837 --- /dev/null +++ b/tools/halidoscope/src/components/profile/panels/ProfileOverview.tsx @@ -0,0 +1,84 @@ +import { Separator } from "radix-ui"; + +import { useProfileContext } from "@/hooks/profile"; + +function ProfileOverview() { + const { pipelines } = useProfileContext(); + + const { + name, + runs, + billed_runs, + samples, + time_ns, + memory_peak, + memory_total, + active_threads_numerator, + active_threads_denominator, + } = pipelines[0]; + + return ( +
+
+ Pipeline + {name} +
+ +
+ Runs + {runs} +
+ +
+ Billed Runs + {billed_runs} +
+ +
+ Samples + {samples} +
+ +
+ Avg. Time / Run + + {time_ns / billed_runs} ns /{" "} + {((time_ns / billed_runs) * 1e-6).toFixed(3)} ms + +
+ +
+ Peak Memory + + {memory_peak}  + B + {" / "} + {(memory_peak * 1e-6).toFixed(3)}  + MB + +
+ +
+ Total Memory + + + {memory_total / runs}  + B + {" / "} + {((memory_total / runs) * 1e-6).toFixed(3)}  + MB + + +
+ +
+ Avg. Threads + + {(active_threads_numerator / active_threads_denominator).toFixed(3)} + +
+
+ ); +} + +export default ProfileOverview; diff --git a/tools/halidoscope/src/components/profile/panels/ProfilePanel.tsx b/tools/halidoscope/src/components/profile/panels/ProfilePanel.tsx new file mode 100644 index 000000000000..8fb28d2d7632 --- /dev/null +++ b/tools/halidoscope/src/components/profile/panels/ProfilePanel.tsx @@ -0,0 +1,34 @@ +import { clsx } from "clsx"; + +interface Props { + label: string; + className?: string; + contentClassName?: string; +} + +function ProfilePanel({ + label, + className = "", + contentClassName = "items-center justify-center p-4", + children, +}: React.PropsWithChildren) { + return ( +
+
+ + {label} + +
+
+ {children} +
+
+ ); +} + +export default ProfilePanel; diff --git a/tools/halidoscope/src/components/profile/panels/ProfileTable.tsx b/tools/halidoscope/src/components/profile/panels/ProfileTable.tsx new file mode 100644 index 000000000000..f58b88a789fa --- /dev/null +++ b/tools/halidoscope/src/components/profile/panels/ProfileTable.tsx @@ -0,0 +1,328 @@ +import { clsx } from "clsx"; +import * as React from "react"; + +import { useProfileContext } from "@/hooks/profile"; +import type { ProfileFunc } from "@/types/profile"; + +// Mirrors the `ProfileFunc.kind` tags documented in `@/types/profile`. +const KIND_OVERHEAD = 1; +const KIND_THREAD_IDLE = 2; +const KIND_MALLOC = 3; +const KIND_FREE = 4; +const KIND_ALLOCATION = 7; + +const SI_SUFFIXES = ["", "K", "M", "G", "T", "P", "E"]; + +/** SI-suffixed byte/allocation counter (10000 -> 10K, 1e6 -> 1.0M, ...), + * matching Halide's `halide_profiler_report`. Zero renders blank. */ +function formatCounter(x: number): string { + if (x <= 0) { + return ""; + } + + let value = x; + let scale = 0; + while (value >= 10000) { + scale++; + value = Math.floor((value + 499) / 1000); + } + + return `${value}${SI_SUFFIXES[scale]}`; +} + +/** A counter accumulated over `runs` runs. Renders the per-run value if + * constant per run, otherwise the average. Zero renders blank. */ +function formatNormalizedCounter(x: number, runs: number): string { + if (x <= 0) { + return ""; + } + if (runs <= 0) { + return formatCounter(x); + } + if (x % runs === 0) { + return formatCounter(x / runs); + } + + const avg = x / runs; + return avg >= 10000 ? formatCounter(Math.round(avg)) : avg.toFixed(2); +} + +/** Time billed to a Func, averaged over the runs the sampler reached. */ +function formatTime(timeNs: number, billedRuns: number): string { + const runs = billedRuns > 0 ? billedRuns : 1; + let value = timeNs / (runs * 1e6); + let unit = "ms"; + + if (value >= 1000) { + value /= 1000; + unit = "s"; + } + + return `${value.toFixed(2)} ${unit}`; +} + +function formatPercent(timeNs: number, pipelineTimeNs: number): string { + const pct = pipelineTimeNs > 0 ? (timeNs / pipelineTimeNs) * 100 : 0; + return `(${pct.toFixed(1)}%)`; +} + +function formatThreads(numerator: number, denominator: number): string { + return denominator > 0 ? (numerator / denominator).toFixed(2) : ""; +} + +interface TreeInfo { + /** DFS pre-order over the compute_at tree; parent === -1 is a root. */ + order: number[]; + depth: number[]; + isLastSibling: boolean[]; +} + +function buildTree(funcs: ProfileFunc[]): TreeInfo { + const n = funcs.length; + const visited = new Array(n).fill(false); + const depth = new Array(n).fill(0); + const isLastSibling = new Array(n).fill(false); + const order: number[] = []; + + const dfs = (parentIdx: number, d: number) => { + let last = -1; + for (let i = 0; i < n; i++) { + if (funcs[i].parent === parentIdx && !visited[i]) { + last = i; + } + } + for (let i = 0; i < n; i++) { + if (funcs[i].parent === parentIdx && !visited[i]) { + visited[i] = true; + depth[i] = d; + isLastSibling[i] = i === last; + order.push(i); + dfs(i, d + 1); + } + } + }; + dfs(-1, 0); + + // Orphans (parent points outside the array) get appended at depth 0 + // rather than being silently dropped. + for (let i = 0; i < n; i++) { + if (!visited[i]) { + depth[i] = 0; + isLastSibling[i] = true; + order.push(i); + } + } + + return { order, depth, isLastSibling }; +} + +interface CumulativeStats { + time_ns: number; + active_threads_numerator: number; + active_threads_denominator: number; +} + +/** Rolls each Func's time and active-thread stats up into its ancestors, for + * the cumulative "active threads" column. */ +function computeCumulativeStats( + funcs: ProfileFunc[], + order: number[], +): CumulativeStats[] { + const cumulative: CumulativeStats[] = funcs.map(() => ({ + time_ns: 0, + active_threads_numerator: 0, + active_threads_denominator: 0, + })); + + // Descendants always sort after their ancestor in `order`, so walking it + // backwards guarantees a node's children are folded in before its own + // total is propagated up to its parent. + for (let i = order.length - 1; i >= 0; i--) { + const j = order[i]; + const cs = cumulative[j]; + cs.time_ns += funcs[j].time_ns; + cs.active_threads_numerator += funcs[j].active_threads_numerator; + cs.active_threads_denominator += funcs[j].active_threads_denominator; + + const parent = funcs[j].parent; + if (parent >= 0) { + const parentCs = cumulative[parent]; + parentCs.time_ns += cs.time_ns; + parentCs.active_threads_numerator += cs.active_threads_numerator; + parentCs.active_threads_denominator += cs.active_threads_denominator; + } + } + + return cumulative; +} + +interface NameCellProps { + func: ProfileFunc; + funcs: ProfileFunc[]; + idx: number; + depth: number; + isLastSibling: boolean[]; +} + +/** Renders a Func's name with tree-art indentation: `│` continues an + * ancestor's subtree, `├`/`└` connects this row to its parent. */ +function NameCell({ func, funcs, idx, depth, isLastSibling }: NameCellProps) { + if (depth === 0) { + return {func.name}; + } + + const lineage: number[] = []; + let j = idx; + for (let k = depth; k > 0; k--) { + lineage[k - 1] = j; + j = funcs[j].parent; + } + + return ( + + + {lineage + .slice(0, depth - 1) + .map((ancestor) => (isLastSibling[ancestor] ? " " : "│")) + .join("")} + {isLastSibling[lineage[depth - 1]] ? "└" : "├"} + + {func.name} + + ); +} + +interface HeaderCellProps { + label: string; + className?: string; +} + +function HeaderCell({ label, className }: HeaderCellProps) { + return ( + +
{label}
+ + ); +} + +/** + * Renders the same per-Func breakdown as Halide's sampling profiler prints + * to stdout (see `halide_profiler_report_unlocked` in `profiler_common.cpp`), + * as an HTML table. + */ +function ProfileTable() { + const { pipelines } = useProfileContext(); + const { funcs, runs, billed_runs, time_ns, num_allocs } = pipelines[0]; + + const { order, depth, isLastSibling } = React.useMemo( + () => buildTree(funcs), + [funcs], + ); + + const cumulative = React.useMemo( + () => computeCumulativeStats(funcs, order), + [funcs, order], + ); + + const rows = React.useMemo( + () => + order.filter((i) => { + const fs = funcs[i]; + if ( + (fs.kind === KIND_OVERHEAD || fs.kind === KIND_THREAD_IDLE) && + fs.time_ns === 0 + ) { + return false; + } + if ( + (fs.kind === KIND_MALLOC || fs.kind === KIND_FREE) && + num_allocs === 0 + ) { + return false; + } + return true; + }), + [funcs, num_allocs, order], + ); + + return ( + + + + + + + + + + + + + + {rows.map((i) => { + const fs = funcs[i]; + const cs = cumulative[i]; + const isAllocation = fs.kind === KIND_ALLOCATION; + const peakMem = fs.num_allocs > 0 ? fs.memory_peak : fs.stack_peak; + const avgMem = + fs.num_allocs > 0 ? Math.floor(fs.memory_total / fs.num_allocs) : 0; + + return ( + + + {isAllocation ? ( + + ) : ( + <> + + + + )} + + + + + + ); + })} + +
+ + + (allocation) + + {formatTime(fs.time_ns, billed_runs)} + + {formatPercent(fs.time_ns, time_ns)} + + {!isAllocation && cs.time_ns > 0 + ? formatThreads( + cs.active_threads_numerator, + cs.active_threads_denominator, + ) + : ""} + + {formatNormalizedCounter(fs.num_allocs, runs)} + + {formatCounter(peakMem)} + + {formatCounter(avgMem)} +
+ ); +} + +export default ProfileTable; diff --git a/tools/halidoscope/src/components/shared/Checkbox.tsx b/tools/halidoscope/src/components/shared/Checkbox.tsx new file mode 100644 index 000000000000..df1de7ba3439 --- /dev/null +++ b/tools/halidoscope/src/components/shared/Checkbox.tsx @@ -0,0 +1,44 @@ +import { Checkbox as RadixCheckbox } from "radix-ui"; + +interface Props { + checked: boolean; + id: string; + label: string; + onCheckedChange: (checked: boolean) => void; +} + +function Checkbox({ checked, id, label, onCheckedChange }: Props) { + return ( +
+ onCheckedChange(!!checked)} + > + + + + + + + +
+ ); +} + +export default Checkbox; diff --git a/tools/halidoscope/src/components/shared/Select.tsx b/tools/halidoscope/src/components/shared/Select.tsx new file mode 100644 index 000000000000..202b2df7d72d --- /dev/null +++ b/tools/halidoscope/src/components/shared/Select.tsx @@ -0,0 +1,98 @@ +import { Label, Select as RadixSelect } from "radix-ui"; + +interface Item { + value: string; + label: string; +} + +interface Props { + id?: string; + label?: string; + value: string; + onValueChange: (value: string) => void; + items: Item[]; +} + +function BaseSelect({ + id, + items, + value, + onValueChange, +}: Exclude) { + return ( + + + + + + + + + + + + + + {items.map((item) => ( + + + {item.label} + + + ))} + + + + ); +} + +function Select({ id, label, value, onValueChange, items }: Props) { + if (!id || !label) { + return ( + + ); + } + + return ( +
+ + {label} + + +
+ ); +} + +export default Select; diff --git a/tools/halidoscope/src/components/trace/Trace.tsx b/tools/halidoscope/src/components/trace/Trace.tsx new file mode 100644 index 000000000000..da5af506d176 --- /dev/null +++ b/tools/halidoscope/src/components/trace/Trace.tsx @@ -0,0 +1,24 @@ +import { ReactFlowProvider } from "@xyflow/react"; + +import Canvas from "@/components/trace/canvas/Canvas"; +import PanelsTabs from "@/components/trace/panels/PanelsTabs"; +import TraceTimeline from "@/components/trace/controls/Timeline"; +import { useTraceContext } from "@/hooks/trace"; + +function Trace() { + const { funcs, dagEdges, packetCount } = useTraceContext(); + + return ( +
+
+ + + + +
+ +
+ ); +} + +export default Trace; diff --git a/tools/halidoscope/src/components/trace/TraceLoading.tsx b/tools/halidoscope/src/components/trace/TraceLoading.tsx new file mode 100644 index 000000000000..e04f9f0914b5 --- /dev/null +++ b/tools/halidoscope/src/components/trace/TraceLoading.tsx @@ -0,0 +1,22 @@ +import { motion } from "motion/react"; + +interface Props { + message: string; + progress: number; +} + +function TraceLoading({ message, progress }: Props) { + return ( +
+

{message}

+
+ +
+
+ ); +} + +export default TraceLoading; diff --git a/tools/halidoscope/src/components/trace/TraceUpload.tsx b/tools/halidoscope/src/components/trace/TraceUpload.tsx new file mode 100644 index 000000000000..df98dda090dd --- /dev/null +++ b/tools/halidoscope/src/components/trace/TraceUpload.tsx @@ -0,0 +1,39 @@ +import { open } from "@tauri-apps/plugin-dialog"; + +interface Props { + onUpload: (path: string) => void; +} + +function TraceUpload({ onUpload }: Props) { + async function handleClick() { + const path = await open({ + multiple: false, + filters: [{ name: "Halide Trace", extensions: ["hltrace"] }], + }); + + if (!path) { + return; + } + + onUpload(path); + } + + return ( +
+ +
+ ); +} + +export default TraceUpload; diff --git a/tools/halidoscope/src/components/trace/canvas/Canvas.tsx b/tools/halidoscope/src/components/trace/canvas/Canvas.tsx new file mode 100644 index 000000000000..2f9b55b0c581 --- /dev/null +++ b/tools/halidoscope/src/components/trace/canvas/Canvas.tsx @@ -0,0 +1,113 @@ +import { + applyEdgeChanges, + ReactFlow, + useNodesState, + useViewport, + type Node, + type Edge, + type EdgeChange, +} from "@xyflow/react"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import * as React from "react"; + +import FuncEdge from "@/components/trace/canvas/FuncEdge"; +import FuncNode from "@/components/trace/canvas/FuncNode"; +import Overlay from "@/components/trace/canvas/Overlay"; +import { funcAtom } from "@/state/func"; +import { edgesAtom } from "@/state/graph"; +import { livenessAtom } from "@/state/liveness"; +import type { FuncMeta } from "@/types/trace"; +import { buildEdges, buildNodes, getLayoutedElements } from "@/utils/graph"; + +const NODE_TYPES = { + funcNode: FuncNode, +}; + +const EDGE_TYPES = { + funcEdge: FuncEdge, +}; + +interface Props { + funcs: Record; + dagEdges: Record; +} + +function Canvas({ funcs, dagEdges }: Props) { + const { nodes: initialNodes, edges: initialEdges } = React.useMemo(() => { + return getLayoutedElements( + buildNodes(funcs, "funcNode"), + buildEdges(dagEdges, "funcEdge"), + ); + }, [funcs, dagEdges]); + const [nodes, _setNodes, onNodesChange] = + useNodesState>(initialNodes); + const [edges, setEdges] = useAtom(edgesAtom); + const setFunc = useSetAtom(funcAtom); + const liveness = useAtomValue(livenessAtom); + const { zoom } = useViewport(); + + React.useEffect(() => { + setEdges(initialEdges); + }, [initialEdges, setEdges]); + + const onEdgesChange = React.useCallback( + (changes: EdgeChange[]) => { + setEdges((eds) => applyEdgeChanges(changes, eds)); + }, + [setEdges], + ); + + React.useEffect(() => { + document.documentElement.style.setProperty("--zoom-level", zoom.toString()); + }, [zoom]); + + return ( +
+ setFunc(node.data.name)} + /> + {liveness.active ? ( + + {liveness.mode === "realizations" ? ( +
+
+
+
+ Buffer Live in Memory +
+ ) : ( +
+
+
+
+
+ Producer +
+
+
+
+
+ Consumer +
+
+ )} + + ) : null} + + Zoom: {Math.round(zoom * 100)}% + +
+ ); +} + +export default Canvas; diff --git a/tools/halidoscope/src/components/trace/canvas/FuncEdge.tsx b/tools/halidoscope/src/components/trace/canvas/FuncEdge.tsx new file mode 100644 index 000000000000..acc7a1719c4f --- /dev/null +++ b/tools/halidoscope/src/components/trace/canvas/FuncEdge.tsx @@ -0,0 +1,76 @@ +import { BaseEdge, getBezierPath, type EdgeProps } from "@xyflow/react"; +import { useAtomValue } from "jotai"; +import * as React from "react"; + +import { useTraceContext } from "@/hooks/trace"; +import { livenessAtom } from "@/state/liveness"; +import { packetAtom } from "@/state/packet"; +import { isEdgeLive } from "@/utils/liveness"; + +function FuncEdge({ + id, + source, + target, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + style, + markerStart, + markerEnd, +}: EdgeProps) { + const [path] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + const gradientId = `produce-consume-gradient-${id}`; + + const { funcs } = useTraceContext(); + const liveness = useAtomValue(livenessAtom); + const packetIndex = useAtomValue(packetAtom); + const isProduceConsumeMode = React.useMemo(() => { + return ( + liveness.active && + liveness.mode === "produce-consume" && + isEdgeLive(funcs, source, target, packetIndex) + ); + }, [liveness, funcs, packetIndex, source, target]); + + return ( + <> + + + + + + + + + ); +} + +export default FuncEdge; diff --git a/tools/halidoscope/src/components/trace/canvas/FuncNode.tsx b/tools/halidoscope/src/components/trace/canvas/FuncNode.tsx new file mode 100644 index 000000000000..f2c6012109fa --- /dev/null +++ b/tools/halidoscope/src/components/trace/canvas/FuncNode.tsx @@ -0,0 +1,351 @@ +import { + getIncomers, + getOutgoers, + Handle, + NodeToolbar, + Position, + useEdges, + useNodes, + useViewport, + type Node, + type NodeProps, +} from "@xyflow/react"; +import { clsx } from "clsx"; +import { useAtomValue, useSetAtom } from "jotai"; +import * as React from "react"; + +import HandleCircle from "@/components/trace/canvas/HandleCircle"; +import { useTraceContext } from "@/hooks/trace"; +import { funcAtom } from "@/state/func"; +import { livenessAtom } from "@/state/liveness"; +import { infAtom, nanAtom } from "@/state/nan-inf"; +import { packetAtom } from "@/state/packet"; +import { renderAtom } from "@/state/render"; +import { tabularDataAtom } from "@/state/tabularData"; +import { threadAtom } from "@/state/thread"; +import type { FuncMeta } from "@/types/trace"; +import { + renderGrayscale, + renderLoadFrequency, + renderRedundantStores, + renderReuseDistance, + renderRgb, + renderStoreFrequency, + renderThread, + type RenderFuncParams, + type RenderFuncResponse, +} from "@/utils/api"; +import { isEdgeLive, isFuncBufferLive } from "@/utils/liveness"; + +function FuncNode({ data }: NodeProps>) { + const { name, width, height, buffer_liveness, max_store_count } = data; + const canvasRef = React.useRef(null); + const nanOverlayRef = React.useRef(null); + const infOverlayRef = React.useRef(null); + + const { funcs } = useTraceContext(); + const liveness = useAtomValue(livenessAtom); + const packetIndex = useAtomValue(packetAtom); + const render = useAtomValue(renderAtom); + const activeFunc = useAtomValue(funcAtom); + const setTabularData = useSetAtom(tabularDataAtom); + const nan = useAtomValue(nanAtom); + const inf = useAtomValue(infAtom); + const thread = useAtomValue(threadAtom); + + const nodes = useNodes(); + const edges = useEdges(); + + const active = activeFunc === name; + const bufferLive = React.useMemo( + () => + liveness.active && + liveness.mode === "realizations" && + isFuncBufferLive(data, packetIndex), + [liveness, data, packetIndex], + ); + const producing = React.useMemo( + () => + liveness.active && + liveness.mode === "produce-consume" && + edges.some( + (edge) => + edge.source === name && + isEdgeLive(funcs, edge.source, edge.target, packetIndex), + ), + [liveness, edges, funcs, name, packetIndex], + ); + const consuming = React.useMemo( + () => + liveness.active && + liveness.mode === "produce-consume" && + edges.some( + (edge) => + edge.target === name && + isEdgeLive(funcs, edge.source, edge.target, packetIndex), + ), + [liveness, edges, funcs, name, packetIndex], + ); + + const incomingEdgeCount = React.useMemo( + () => getIncomers({ id: name }, nodes, edges).length, + [name, nodes, edges], + ); + const outgoingEdgeCount = React.useMemo( + () => getOutgoers({ id: name }, nodes, edges).length, + [name, nodes, edges], + ); + + const { zoom } = useViewport(); + + // Track the playhead position as a ref to avoid re-rendering on every scrub. + const latestIndexRef = React.useRef(packetIndex); + + // Track whether an active render is in progress. + const renderingRef = React.useRef(false); + + // Cache render responses if we are outside of a Func's liveness range. + // In addition, add a useEffect call to invalidate the cache if any + // application state affecting the RenderFuncResponse changes. + const cachedPreLiveResultRef = React.useRef(null); + const cachedPostLiveResultRef = React.useRef(null); + + React.useEffect(() => { + cachedPreLiveResultRef.current = null; + cachedPostLiveResultRef.current = null; + }, [render, nan, inf, thread, active]); + + React.useEffect(() => { + latestIndexRef.current = packetIndex; + + // Return early if we're actively writing a tensor. + if (renderingRef.current) { + return; + } + + renderingRef.current = true; + + async function draw() { + try { + // Funcs with no stores (pipeline inputs) never see a Begin/EndRealization + // pair, so `buffer_liveness` defaults to (0, 0) rather than a real teardown + // point — treat those as always live instead of "dead" past index 0. + const isRealized = max_store_count > 0; + + while (true) { + const target = latestIndexRef.current; + const notYetLive = target < buffer_liveness.start; + const noLongerLive = isRealized && target > buffer_liveness.end; + + let result: RenderFuncResponse; + + if (notYetLive && cachedPreLiveResultRef.current) { + result = cachedPreLiveResultRef.current; + } else if (noLongerLive && cachedPostLiveResultRef.current) { + result = cachedPostLiveResultRef.current; + } else { + const params: RenderFuncParams = { + func: name, + globalIndex: target, + normalizationMode: render.normalizationMode, + width, + height, + includeTabularData: active, + includeNan: { + active: nan.active, + ...nan.color, + }, + includeInf: { + active: inf.active, + ...inf.color, + }, + }; + + switch (render.renderMode) { + case "Grayscale": + result = await renderGrayscale(params); + break; + case "RGB": + result = await renderRgb(params); + break; + case "Store Frequency": + result = await renderStoreFrequency(params); + break; + case "Load Frequency": + result = await renderLoadFrequency(params); + break; + case "Redundant Stores": + result = await renderRedundantStores(params); + break; + case "Reuse Distance": + result = await renderReuseDistance(params); + break; + case "Thread Coverage": + result = await renderThread({ + ...params, + threadOpMode: thread.op, + threadId: thread.id, + }); + break; + } + + // Cache the fetch if we fall outside the Func's buffer liveness range. + if (notYetLive) { + cachedPreLiveResultRef.current = result; + } else if (noLongerLive) { + cachedPostLiveResultRef.current = result; + } + } + + const ctx = canvasRef.current?.getContext("2d"); + if (ctx) { + ctx.putImageData( + new ImageData(result.tensorData, width, height), + 0, + 0, + ); + } + + const nanCtx = nanOverlayRef.current?.getContext("2d"); + if (nanCtx && result.nanOverlayData) { + nanCtx.putImageData( + new ImageData(result.nanOverlayData, width, height), + 0, + 0, + ); + } + + const infCtx = infOverlayRef.current?.getContext("2d"); + if (infCtx && result.infOverlayData) { + infCtx.putImageData( + new ImageData(result.infOverlayData, width, height), + 0, + 0, + ); + } + + // Update the histogram data for the currently active Func. + if (active) { + setTabularData((prev) => ({ + ...prev, + tabularData: result.tabularData, + })); + } + + if (latestIndexRef.current === target) { + break; + } + } + } catch (err) { + console.error( + `Failed to render ${name} at index ${latestIndexRef.current}: ${err}`, + ); + } finally { + renderingRef.current = false; + } + } + + draw(); + }, [ + active, + packetIndex, + name, + width, + height, + render, + activeFunc, + setTabularData, + thread, + nan, + inf, + buffer_liveness.start, + buffer_liveness.end, + max_store_count, + ]); + + return ( + <> + + = 0.5, + })} + > + {name} + + +
= 1, + })} + > + = 1, + })} + /> + + +
+ {incomingEdgeCount > 0 && edges.every((edge) => !edge.hidden) ? ( + + + + ) : null} + {outgoingEdgeCount > 0 && edges.every((edge) => !edge.hidden) ? ( + + + + ) : null} + + ); +} + +export default FuncNode; diff --git a/tools/halidoscope/src/components/trace/canvas/HandleCircle.tsx b/tools/halidoscope/src/components/trace/canvas/HandleCircle.tsx new file mode 100644 index 000000000000..db01602170e8 --- /dev/null +++ b/tools/halidoscope/src/components/trace/canvas/HandleCircle.tsx @@ -0,0 +1,21 @@ +import { useViewport } from "@xyflow/react"; + +function HandleCircle() { + const { zoom } = useViewport(); + + return ( + + + + ); +} + +export default HandleCircle; diff --git a/tools/halidoscope/src/components/trace/canvas/Overlay.tsx b/tools/halidoscope/src/components/trace/canvas/Overlay.tsx new file mode 100644 index 000000000000..0956f5294609 --- /dev/null +++ b/tools/halidoscope/src/components/trace/canvas/Overlay.tsx @@ -0,0 +1,20 @@ +import { clsx } from "clsx"; +import type * as React from "react"; + +function Overlay({ + children, + className, +}: React.PropsWithChildren<{ className: string }>) { + return ( +
+ {children} +
+ ); +} + +export default Overlay; diff --git a/tools/halidoscope/src/components/trace/charts/BarChart.tsx b/tools/halidoscope/src/components/trace/charts/BarChart.tsx new file mode 100644 index 000000000000..71ec872c6b98 --- /dev/null +++ b/tools/halidoscope/src/components/trace/charts/BarChart.tsx @@ -0,0 +1,89 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; +import * as React from "react"; + +interface Props { + data: { x: string; y: number }[]; + domain: string[]; + range: string[]; + labels: { + x: string; + y: string; + }; + highlight?: (x: string) => boolean; +} + +function BarChart({ data, domain, range, labels, highlight }: Props) { + const ref = React.useRef(null); + + React.useEffect(() => { + if (!ref.current) { + return; + } + + const plot = Plot.plot({ + style: { + fontSize: "12px", + }, + width: 480, + marginBottom: 80, + y: { + grid: true, + label: labels.y, + tickFormat: (value) => d3.format(".2s")(value), + ticks: 8, + }, + x: { + domain, + label: labels.x, + labelAnchor: "right", + labelArrow: "right", + tickSize: 0, + tickRotate: -45, + type: "band", + }, + color: { + domain, + range, + }, + marks: [ + Plot.barY(data, { + x: "x", + y: "y", + fill: "x", + fillOpacity: (d) => (highlight?.(d.x) ? 1 : 0.25), + }), + ], + }); + + ref.current.append(plot); + + return () => { + plot.remove(); + }; + }, [data, labels, domain, range, highlight]); + + return data.every((d) => d.y === 0) ? ( +
+ + + + No data to display +
+ ) : ( +
+ ); +} + +export default BarChart; diff --git a/tools/halidoscope/src/components/trace/charts/Histogram.tsx b/tools/halidoscope/src/components/trace/charts/Histogram.tsx new file mode 100644 index 000000000000..57330cc9882a --- /dev/null +++ b/tools/halidoscope/src/components/trace/charts/Histogram.tsx @@ -0,0 +1,110 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; +import * as React from "react"; + +import type { Scale } from "@/state/tabularData"; + +interface Props { + data: { x1: number; x2: number; y0: number; y1: number; color: string }[]; + domain: [number, number]; + scale: Scale; + labels: { + x: string; + y: string; + }; + renderLegend: boolean; + interval?: number; +} + +function Histogram({ + data, + domain, + scale, + labels, + renderLegend, + interval, +}: Props) { + const ref = React.useRef(null); + + React.useEffect(() => { + if (!ref.current || data.length === 0) { + return; + } + + const plot = Plot.plot({ + style: { + fontSize: "12px", + }, + width: 480, + marginBottom: 60, + y: { + grid: true, + label: labels.y, + tickFormat: (value) => d3.format(".2s")(value), + ticks: 8, + }, + x: { + domain, + label: labels.x, + labelAnchor: "right", + labelArrow: "right", + tickFormat: (value) => d3.format(".2s")(value), + tickPadding: 24, + tickSize: 0, + type: scale, + interval, + }, + marks: [ + Plot.rect(data, { + x1: "x1", + x2: "x2", + y1: "y0", + y2: "y1", + fill: "color", + }), + ...(renderLegend + ? [ + Plot.ruleY(data, { + stroke: "color", + strokeWidth: 8, + x1: "x1", + x2: "x2", + y: 0, + dy: 12, + }), + ] + : []), + ], + }); + + ref.current.append(plot); + + return () => { + plot.remove(); + }; + }, [data, domain, labels, scale, renderLegend, interval]); + + return data.every((d) => d.y1 === d.y0) ? ( +
+ + + + No data to display +
+ ) : ( +
+ ); +} + +export default Histogram; diff --git a/tools/halidoscope/src/components/trace/controls/ColorInput.tsx b/tools/halidoscope/src/components/trace/controls/ColorInput.tsx new file mode 100644 index 000000000000..b8b12874af7a --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/ColorInput.tsx @@ -0,0 +1,118 @@ +import * as d3 from "d3"; +import { Label } from "radix-ui"; +import * as React from "react"; + +interface Props { + id: string; + color: string; + defaultValue: string; + onChangeColor: (color: string) => void; + alpha: number; + onChangeAlpha: (opacity: number) => void; +} + +const hexPattern = /^#([0-9A-Fa-f]{3}){1,2}$/i; + +function ColorInput({ + id, + color, + defaultValue, + onChangeColor, + alpha, + onChangeAlpha, +}: Props) { + const [colorLocal, setColorLocal] = React.useState(color); + const [alphaLocal, setAlphaLocal] = React.useState(alpha); + + const onChange = React.useCallback( + (event: React.ChangeEvent) => { + let output = event.currentTarget.value; + setColorLocal(output); + + if (!output.startsWith("#")) { + output = "#" + output; + } + + if (hexPattern.test(output)) { + onChangeColor(d3.color(output)?.formatHex() ?? defaultValue); + } + }, + [defaultValue, onChangeColor], + ); + + const onBlur = React.useCallback(() => { + let c = color; + + if (!color.startsWith("#")) { + c = "#" + c; + } + + if (!hexPattern.test(c)) { + setColorLocal(defaultValue); + onChangeColor(defaultValue); + } + }, [color, onChangeColor, defaultValue]); + + const onChangeAlphaLocal = React.useCallback( + (event: React.ChangeEvent) => { + const output = Number(event.currentTarget.value); + setAlphaLocal(output); + + if (output >= 0 && output <= 100) { + onChangeAlpha(output); + } + }, + [setAlphaLocal, onChangeAlpha], + ); + + const onBlurAlphaLocal = React.useCallback(() => { + const a = alphaLocal; + + if (Number.isNaN(a)) { + setAlphaLocal(alpha); + } else if (a < 0) { + setAlphaLocal(0); + onChangeAlpha(0); + } else if (a > 100) { + setAlphaLocal(100); + onChangeAlpha(100); + } + }, [alpha, alphaLocal, onChangeAlpha]); + + return ( +
+ + Color + +
+ + + + % +
+
+ ); +} + +export default ColorInput; diff --git a/tools/halidoscope/src/components/trace/controls/GraphDisplay.tsx b/tools/halidoscope/src/components/trace/controls/GraphDisplay.tsx new file mode 100644 index 000000000000..51f3851d0e17 --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/GraphDisplay.tsx @@ -0,0 +1,36 @@ +import { type Edge } from "@xyflow/react"; +import { useSetAtom } from "jotai"; +import * as React from "react"; + +import Checkbox from "@/components/shared/Checkbox"; +import { edgesAtom } from "@/state/graph"; + +function hideEdge(hidden: boolean) { + return function handleVisibilityChange(edge: Edge) { + return { + ...edge, + hidden, + }; + }; +} + +function GraphDisplay() { + const [visible, setVisible] = React.useState(true); + const setEdges = useSetAtom(edgesAtom); + + function onEdgeVisibilityChange(checked: boolean) { + setVisible(checked); + setEdges((eds) => eds.map(hideEdge(!checked))); + } + + return ( + + ); +} + +export default GraphDisplay; diff --git a/tools/halidoscope/src/components/trace/controls/InfControls.tsx b/tools/halidoscope/src/components/trace/controls/InfControls.tsx new file mode 100644 index 000000000000..7cd416568ad0 --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/InfControls.tsx @@ -0,0 +1,65 @@ +import * as d3 from "d3"; +import { useAtom } from "jotai"; + +import ColorInput from "@/components/trace/controls/ColorInput"; +import Checkbox from "@/components/shared/Checkbox"; +import Select from "@/components/shared/Select"; +import { + DEFAULT_INF_COLOR, + infAtom, + ANIMATION_MODES, + type AnimationMode, +} from "@/state/nan-inf"; + +function InfControls() { + const [inf, setInf] = useAtom(infAtom); + + return ( +
+ setInf({ ...inf, active })} + /> + {inf.active ? ( +
+ + setNan({ ...nan, animationMode: value as AnimationMode }) + } + items={ANIMATION_MODES.map((mode) => ({ + value: mode, + label: mode, + }))} + /> + { + const { r, g, b } = d3.color(color)?.rgb() ?? { + r: 0, + g: 0, + b: 0, + }; + setNan({ ...nan, color: { ...nan.color, r, g, b } }); + }} + alpha={Math.round(nan.color.a * 100)} + onChangeAlpha={(alpha) => { + setNan({ + ...nan, + color: { ...nan.color, a: alpha / 100 }, + }); + }} + /> +
+ ) : null} +
+ ); +} + +export default NaNControls; diff --git a/tools/halidoscope/src/components/trace/controls/PlaybackRate.tsx b/tools/halidoscope/src/components/trace/controls/PlaybackRate.tsx new file mode 100644 index 000000000000..21f03c64461f --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/PlaybackRate.tsx @@ -0,0 +1,50 @@ +import { useAtom } from "jotai"; +import { Label, Slider } from "radix-ui"; + +import { playbackRateAtom, DEFAULT_PLAYBACK_RATE } from "@/state/playback"; + +const MIN_RATE = 100; +const MAX_RATE = 20_000; +const STEP = 100; + +function PlaybackRate() { + const [playbackRate, setPlaybackRate] = useAtom(playbackRateAtom); + + return ( +
+ + Playback Rate (Packets / Tick) + +
+ setPlaybackRate(value[0])} + className="relative flex h-4 flex-1 items-center" + > + + + + + + setPlaybackRate(Number(e.target.value))} + min={MIN_RATE} + max={MAX_RATE} + step={STEP} + /> +
+
+ ); +} + +export default PlaybackRate; diff --git a/tools/halidoscope/src/components/trace/controls/RenderMode.tsx b/tools/halidoscope/src/components/trace/controls/RenderMode.tsx new file mode 100644 index 000000000000..1e52ffd05941 --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/RenderMode.tsx @@ -0,0 +1,20 @@ +import { useAtom } from "jotai"; + +import Select from "@/components/shared/Select"; +import { renderAtom, RENDER_MODES, type RenderMode } from "@/state/render"; + +function RenderMode() { + const [render, setRender] = useAtom(renderAtom); + + return ( + + setTabularData({ ...tabularData, scale: value as Scale }) + } + items={[ + { value: "linear", label: "Linear" }, + { value: "log", label: "Log" }, + ]} + /> + { + setThread({ ...thread, op: value as "Load" | "Store" }); + }} + items={[ + { value: "Store", label: "Store" }, + { value: "Load", label: "Load" }, + ]} + /> + ({ + value: func, + label: func, + }))} + /> + {renderSecondaryControls()} +
+ ); +} + +export default RenderModeParameters; diff --git a/tools/halidoscope/src/components/trace/controls/Timeline.tsx b/tools/halidoscope/src/components/trace/controls/Timeline.tsx new file mode 100644 index 000000000000..f1f83482ec2f --- /dev/null +++ b/tools/halidoscope/src/components/trace/controls/Timeline.tsx @@ -0,0 +1,172 @@ +import * as d3 from "d3"; +import { useAtom, useSetAtom } from "jotai"; +import { Slider } from "radix-ui"; +import * as React from "react"; + +import { packetAtom } from "@/state/packet"; +import { playbackRateAtom } from "@/state/playback"; + +const SCRUB_DEBOUNCE_MS = 50; + +interface Props { + packetCount: number; +} + +function Timeline({ packetCount }: Props) { + // Local slider position, for a smooth thumb independent of render cadence. + const [packetIndex, setPacketIndex] = React.useState(0); + const [playing, setPlaying] = React.useState(false); + const [playbackRate] = useAtom(playbackRateAtom); + const setGlobalIndex = useSetAtom(packetAtom); + + // Mirror the latest index synchronously for the playback interval closure. + const indexRef = React.useRef(0); + const scrubTimerRef = React.useRef(null); + + const commitIndex = React.useCallback((next: number) => { + indexRef.current = next; + setPacketIndex(next); + }, []); + + // Scrub: move the thumb immediately, but defer the (debounced) render so a + // drag doesn't fire a request per intermediate position. FuncCanvases + // coalesce in flight, but debouncing still trims redundant render cycles. + const onScrub = React.useCallback( + (next: number) => { + commitIndex(next); + + if (scrubTimerRef.current !== null) { + window.clearTimeout(scrubTimerRef.current); + } + + scrubTimerRef.current = window.setTimeout(() => { + scrubTimerRef.current = null; + setGlobalIndex(next); + }, SCRUB_DEBOUNCE_MS); + }, + [commitIndex, setGlobalIndex], + ); + + const onTogglePlay = React.useCallback(() => { + // Starting from the end replays from the beginning. + if (!playing && indexRef.current >= packetCount - 1) { + commitIndex(0); + setGlobalIndex(0); + } + + setPlaying((p) => !p); + }, [playing, packetCount, commitIndex, setGlobalIndex]); + + // Playback loop: advance the playhead on a fixed interval and push each step + // straight to the global index. Canvases coalesce if rendering lags. + React.useEffect(() => { + if (!playing) { + return; + } + + let animationFrameId: number | null = null; + + function step() { + const next = Math.min(indexRef.current + playbackRate, packetCount - 1); + commitIndex(next); + setGlobalIndex(next); + + if (next >= packetCount - 1 && animationFrameId !== null) { + setPlaying(false); + cancelAnimationFrame(animationFrameId); + } + + animationFrameId = requestAnimationFrame(step); + } + + step(); + + return () => { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + } + }; + }, [packetCount, playing, commitIndex, setGlobalIndex, playbackRate]); + + const disabled = packetCount <= 0; + const ticks = d3 + .ticks(0, packetCount - 1, 10) + .filter((t) => t > 0 && t < packetCount - 1); + + return ( +
+
+ +
+ {ticks.map((tick) => ( +
+

+ {d3.format(".2s")(tick)} +

+
+ ))} + onScrub(values[0])} + value={[packetIndex]} + disabled={disabled} + > + + + + + +
+
+
+ Packets + + {packetIndex.toLocaleString()} /{" "} + {Math.max(packetCount - 1, 0).toLocaleString()} + +
+
+ ); +} + +export default Timeline; diff --git a/tools/halidoscope/src/components/trace/panels/DebugPanel.tsx b/tools/halidoscope/src/components/trace/panels/DebugPanel.tsx new file mode 100644 index 000000000000..7d89b731d521 --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/DebugPanel.tsx @@ -0,0 +1,27 @@ +import { Separator } from "radix-ui"; + +import InfControls from "@/components/trace/controls/InfControls"; +import LivenessControls from "@/components/trace/controls/LivenessControls"; +import NaNControls from "@/components/trace/controls/NaNControls"; +import PanelSection from "@/components/trace/panels/PanelSection"; + +function DebugPanel() { + return ( +
+ + + + + + + + + + + + +
+ ); +} + +export default DebugPanel; diff --git a/tools/halidoscope/src/components/trace/panels/DisplayPanel.tsx b/tools/halidoscope/src/components/trace/panels/DisplayPanel.tsx new file mode 100644 index 000000000000..5894d25f2eb2 --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/DisplayPanel.tsx @@ -0,0 +1,21 @@ +import { Separator } from "radix-ui"; + +import GraphDisplay from "@/components/trace/controls/GraphDisplay"; +import PlaybackRate from "@/components/trace/controls/PlaybackRate"; +import PanelSection from "@/components/trace/panels/PanelSection"; + +function DisplayPanel() { + return ( +
+ + + + + + + +
+ ); +} + +export default DisplayPanel; diff --git a/tools/halidoscope/src/components/trace/panels/FuncsPanel.tsx b/tools/halidoscope/src/components/trace/panels/FuncsPanel.tsx new file mode 100644 index 000000000000..0399a0d1147a --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/FuncsPanel.tsx @@ -0,0 +1,92 @@ +import { useAtom } from "jotai"; +import { Accordion } from "radix-ui"; + +import { funcAtom } from "@/state/func"; +import type { FuncMeta } from "@/types/trace"; + +interface Props { + funcs: Record; +} + +function FuncsPanel({ funcs }: Props) { + const [func, setFunc] = useAtom(funcAtom); + + return ( + setFunc(value)} + > + {Object.values(funcs).map((func) => ( + + + + + + {func.name} + + +
+ + Minimum Coordinates + + + ({func.min_coords.join(",")}) + + + Maximum Coordinates + + + ({func.max_coords.join(",")}) + + + Minimum Value + + + {func.min_value} + + + Maximum Value + + + {func.max_value} + + + Maximum Store Count + + + {func.max_store_count.toLocaleString()} + + + Maximum Load Count + + + {func.max_load_count.toLocaleString()} + + + Thread Count + + + {func.thread_count.toLocaleString()} + +
+
+
+ ))} +
+ ); +} + +export default FuncsPanel; diff --git a/tools/halidoscope/src/components/trace/panels/PanelSection.tsx b/tools/halidoscope/src/components/trace/panels/PanelSection.tsx new file mode 100644 index 000000000000..a1b27bd66dbe --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/PanelSection.tsx @@ -0,0 +1,19 @@ +import { Label } from "radix-ui"; +import type * as React from "react"; + +interface Props { + title: string; +} + +function PanelSection({ title, children }: React.PropsWithChildren) { + return ( +
+ + {title} + +
{children}
+
+ ); +} + +export default PanelSection; diff --git a/tools/halidoscope/src/components/trace/panels/PanelsTabs.tsx b/tools/halidoscope/src/components/trace/panels/PanelsTabs.tsx new file mode 100644 index 000000000000..9eb27cec1d14 --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/PanelsTabs.tsx @@ -0,0 +1,70 @@ +import { Tabs } from "radix-ui"; + +import DebugPanel from "@/components/trace/panels/DebugPanel"; +import DisplayPanel from "@/components/trace/panels/DisplayPanel"; +import FuncsPanel from "@/components/trace/panels/FuncsPanel"; +import VisualizationPanel from "@/components/trace/panels/VisualizationPanel"; +import { FuncMeta } from "@/types/trace"; + +function PanelsTabs({ funcs }: { funcs: Record }) { + return ( +
+
+
+
+ + + + Funcs + + + Visualization + + + Debug + + + + + + + + + + + + + + + + Display + + + + + + +
+
+ ); +} + +export default PanelsTabs; diff --git a/tools/halidoscope/src/components/trace/panels/VisualizationPanel.tsx b/tools/halidoscope/src/components/trace/panels/VisualizationPanel.tsx new file mode 100644 index 000000000000..f91fbf5cb7eb --- /dev/null +++ b/tools/halidoscope/src/components/trace/panels/VisualizationPanel.tsx @@ -0,0 +1,364 @@ +import * as d3 from "d3"; +import { useAtomValue } from "jotai"; +import { Separator } from "radix-ui"; +import * as React from "react"; + +import RenderMode from "@/components/trace/controls/RenderMode"; +import RenderModeParameters from "@/components/trace/controls/RenderModeParameters"; +import BarChart from "@/components/trace/charts/BarChart"; +import Histogram from "@/components/trace/charts/Histogram"; +import PanelSection from "@/components/trace/panels/PanelSection"; +import { useTraceContext } from "@/hooks/trace"; +import { funcAtom } from "@/state/func"; +import { type RenderMode as RM, renderAtom } from "@/state/render"; +import { tabularDataAtom } from "@/state/tabularData"; +import { threadAtom, NO_THREAD_INFO_SENTINEL_ID } from "@/state/thread"; + +const RENDER_MODE_TO_LABEL: Record = { + Grayscale: "Value", + RGB: "Value", + "Store Frequency": "Store Count", + "Load Frequency": "Load Count", + "Redundant Stores": "Redundant Store Count", + "Reuse Distance": "Reuse Distance (Packets)", + "Thread Coverage": "Thread ID", +}; + +const METRIC_PALETTE = [ + "#0078D1", + "#1695F3", + "#3DACFF", + "#70C2FF", + "#D6EEFF", + "#FFE2D6", + "#FFBFA3", + "#FF773D", + "#FA6400", + "#D64000", +]; + +const PURE_CHANNEL_COLORS: Record<"r" | "g" | "b", string> = { + r: "#ff0000", + g: "#00ff00", + b: "#0000ff", +}; + +const PAIR_CHANNEL_COLORS: Record = { + bg: "#00ffff", + br: "#ff00ff", + gr: "#ffff00", +}; + +const TRIPLE_CHANNEL_COLOR = "#ffffff"; + +interface HistogramData { + type: "Histogram"; + data: { x1: number; x2: number; y0: number; y1: number; color: string }[]; + domain: [number, number]; + renderLegend: boolean; +} + +interface BarChartData { + type: "Bar Chart"; + data: { x: string; y: number }[]; + domain: string[]; + range: string[]; +} + +type ChartData = HistogramData | BarChartData; + +function VisualizationPanel() { + const { funcs, stats } = useTraceContext(); + const render = useAtomValue(renderAtom); + const activeFunc = useAtomValue(funcAtom); + const { tabularData, scale } = useAtomValue(tabularDataAtom); + const thread = useAtomValue(threadAtom); + + const createHistogramData = React.useCallback( + ( + histogramData: Uint32Array, + domain: [number, number], + range: string[], + ): HistogramData => { + const buckets = histogramData.length; + const extent = domain[1] - domain[0]; + const step = + domain.every(Number.isInteger) && extent <= 64 ? 1 : extent / buckets; + const colorScale = d3 + .scaleLinear() + .domain( + range.map( + (_, i) => domain[0] + (i * extent) / (range.length - 1 || 1), + ), + ) + .range(range) + .interpolate(d3.interpolateRgb); + + return { + type: "Histogram", + data: new Array(buckets).fill(0).map((_, i) => { + const x1 = domain[0] + i * step; + + return { + x1, + x2: domain[0] + (i + 1) * step, + y0: 0, + y1: histogramData?.[i] ?? 0, + color: colorScale(x1), + }; + }), + domain: [domain[0], domain[1] + step], + renderLegend: true, + }; + }, + [], + ); + + const createRgbHistogramData = React.useCallback( + ( + channelCounts: [Uint32Array, Uint32Array, Uint32Array], + domain: [number, number], + ): HistogramData => { + const [rCounts, gCounts, bCounts] = channelCounts; + const buckets = rCounts.length; + const extent = domain[1] - domain[0]; + const step = + domain.every(Number.isInteger) && extent <= 64 ? 1 : extent / buckets; + + const data: HistogramData["data"] = []; + + for (let i = 0; i < buckets; i++) { + const x1 = domain[0] + i * step; + const x2 = domain[0] + (i + 1) * step; + const entries = ( + [ + ["r", rCounts[i] ?? 0], + ["g", gCounts[i] ?? 0], + ["b", bCounts[i] ?? 0], + ] as const + ) + .slice() + .sort((a, b) => a[1] - b[1]); + const [lo, mid, hi] = entries; + + // Stack up to three bands per bucket, from the ground up: the height all three + // channels share (white), then the height the top two share (their pairwise + // combination), then the remainder of the tallest channel alone (its pure color). + if (lo[1] > 0) { + data.push({ x1, x2, y0: 0, y1: lo[1], color: TRIPLE_CHANNEL_COLOR }); + } + + if (mid[1] > lo[1]) { + const pairKey = [mid[0], hi[0]].sort().join(""); + data.push({ + x1, + x2, + y0: lo[1], + y1: mid[1], + color: PAIR_CHANNEL_COLORS[pairKey], + }); + } + + if (hi[1] > mid[1]) { + data.push({ + x1, + x2, + y0: mid[1], + y1: hi[1], + color: PURE_CHANNEL_COLORS[hi[0]], + }); + } + } + + return { + type: "Histogram", + data, + domain: [domain[0], domain[1] + step], + renderLegend: false, + }; + }, + [], + ); + + const chartData = React.useMemo((): ChartData => { + switch (render.renderMode) { + case "Grayscale": { + const min = funcs[activeFunc].min_value ?? 0; + const max = funcs[activeFunc].max_value ?? 255; + + return createHistogramData( + tabularData ?? new Uint32Array(), + [min, max], + ["#000000", "#ffffff"], + ); + } + case "RGB": { + const min = funcs[activeFunc].min_value ?? 0; + const max = funcs[activeFunc].max_value ?? 255; + const domain: [number, number] = [min, max]; + const bins = 256; + const numChannels = tabularData + ? Math.floor(tabularData.length / bins) + : 0; + + if (numChannels >= 3) { + return createRgbHistogramData( + [ + tabularData!.slice(0, bins), + tabularData!.slice(bins, bins * 2), + tabularData!.slice(bins * 2, bins * 3), + ], + domain, + ); + } + + return createHistogramData(tabularData ?? new Uint32Array(), domain, [ + "#000000", + "#ffffff", + ]); + } + case "Store Frequency": { + const min = scale === "log" ? 1 : 0; + const max = + render.normalizationMode === "Per Func" + ? funcs[activeFunc].max_store_count + : stats.global_max_store_count; + + return createHistogramData( + tabularData ?? new Uint32Array(), + [min, max], + METRIC_PALETTE, + ); + } + case "Load Frequency": { + const min = scale === "log" ? 1 : 0; + const max = + render.normalizationMode === "Per Func" + ? funcs[activeFunc].max_load_count + : stats.global_max_load_count; + + return createHistogramData( + tabularData ?? new Uint32Array(), + [min, max], + METRIC_PALETTE, + ); + } + case "Redundant Stores": { + const min = scale === "log" ? 1 : 0; + const max = + render.normalizationMode === "Per Func" + ? funcs[activeFunc].max_redundant_store_count + : stats.global_max_redundant_store_count; + + return createHistogramData( + tabularData ?? new Uint32Array(), + [min, max], + METRIC_PALETTE, + ); + } + case "Reuse Distance": { + const min = scale === "log" ? 1 : 0; + const max = + render.normalizationMode === "Per Func" + ? funcs[activeFunc].max_reuse_distance + : stats.global_max_reuse_distance; + + return createHistogramData( + tabularData ?? new Uint32Array(), + [min, max], + METRIC_PALETTE, + ); + } + case "Thread Coverage": { + const threadIds = funcs[activeFunc].thread_ids; + const storeCounts = tabularData?.slice(0, threadIds.length) ?? []; + const loadCounts = + tabularData?.slice(threadIds.length, threadIds.length * 2) ?? []; + + return { + type: "Bar Chart", + data: threadIds.map((threadId, i) => ({ + x: `${threadId}`, + y: thread.op === "Store" ? storeCounts[i] : loadCounts[i], + })), + domain: threadIds.map((tId) => `${tId}`), + range: stats.global_thread_ids.reduce((acc, el, i) => { + if (threadIds.includes(el)) { + return acc.concat(d3.schemeSet3[i]); + } + + return acc; + }, []), + }; + } + } + }, [ + render, + tabularData, + funcs, + stats, + activeFunc, + scale, + thread.op, + createHistogramData, + createRgbHistogramData, + ]); + + const renderChart = React.useCallback(() => { + switch (chartData.type) { + case "Histogram": + return ( + + ); + case "Bar Chart": + return ( + + x === thread.id || thread.id === NO_THREAD_INFO_SENTINEL_ID + } + /> + ); + } + }, [chartData, scale, render.renderMode, thread]); + + return ( +
+ + + + + + + {renderChart()} + + +
+ ); +} + +export default VisualizationPanel; diff --git a/tools/halidoscope/src/hooks/profile.ts b/tools/halidoscope/src/hooks/profile.ts new file mode 100644 index 000000000000..c938c4afe674 --- /dev/null +++ b/tools/halidoscope/src/hooks/profile.ts @@ -0,0 +1,10 @@ +import * as React from "react"; + +import type { Profile } from "@/types/profile"; + +const Profile = React.createContext({ + pipelines: [], +}); + +export const ProfileContextProvider = Profile.Provider; +export const useProfileContext = () => React.useContext(Profile); diff --git a/tools/halidoscope/src/hooks/trace.ts b/tools/halidoscope/src/hooks/trace.ts new file mode 100644 index 000000000000..f519f113845b --- /dev/null +++ b/tools/halidoscope/src/hooks/trace.ts @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { FuncMeta, StatsMeta } from "@/types/trace"; + +const TraceContext = React.createContext<{ + funcs: Record; + dagEdges: Record; + packetCount: number; + stats: StatsMeta; +}>({ + funcs: {}, + dagEdges: {}, + packetCount: 0, + stats: { + global_max_store_count: 0, + global_max_load_count: 0, + global_max_redundant_store_count: 0, + global_max_reuse_distance: 0, + global_thread_ids: [], + }, +}); + +export const TraceContextProvider = TraceContext.Provider; +export const useTraceContext = () => React.useContext(TraceContext); diff --git a/tools/halidoscope/src/main.tsx b/tools/halidoscope/src/main.tsx new file mode 100644 index 000000000000..2be325ed2578 --- /dev/null +++ b/tools/halidoscope/src/main.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/tools/halidoscope/src/state/func.ts b/tools/halidoscope/src/state/func.ts new file mode 100644 index 000000000000..21b50843c1d9 --- /dev/null +++ b/tools/halidoscope/src/state/func.ts @@ -0,0 +1,3 @@ +import { atom } from "jotai"; + +export const funcAtom = atom(""); diff --git a/tools/halidoscope/src/state/graph.ts b/tools/halidoscope/src/state/graph.ts new file mode 100644 index 000000000000..05f06f0d68c0 --- /dev/null +++ b/tools/halidoscope/src/state/graph.ts @@ -0,0 +1,4 @@ +import { type Edge } from "@xyflow/react"; +import { atom } from "jotai"; + +export const edgesAtom = atom([]); diff --git a/tools/halidoscope/src/state/liveness.ts b/tools/halidoscope/src/state/liveness.ts new file mode 100644 index 000000000000..a15c6c81b83e --- /dev/null +++ b/tools/halidoscope/src/state/liveness.ts @@ -0,0 +1,8 @@ +import { atom } from "jotai"; + +export type LivenessMode = "realizations" | "produce-consume"; + +export const livenessAtom = atom<{ active: boolean; mode: LivenessMode }>({ + active: false, + mode: "realizations", +}); diff --git a/tools/halidoscope/src/state/nan-inf.ts b/tools/halidoscope/src/state/nan-inf.ts new file mode 100644 index 000000000000..a315261eb7f6 --- /dev/null +++ b/tools/halidoscope/src/state/nan-inf.ts @@ -0,0 +1,50 @@ +import { atom } from "jotai"; + +export const ANIMATION_MODES = ["Blink", "Pulse", "None"] as const; +export type AnimationMode = (typeof ANIMATION_MODES)[number]; + +export const DEFAULT_NAN_COLOR = "#00ffff"; +export const DEFAULT_NAN_ALPHA = 1; + +export const nanAtom = atom<{ + active: boolean; + animationMode: AnimationMode; + color: { + r: number; + g: number; + b: number; + a: number; + }; +}>({ + active: false, + animationMode: "Blink", + color: { + r: 0, + g: 255, + b: 255, + a: DEFAULT_NAN_ALPHA, + }, +}); + +export const DEFAULT_INF_COLOR = "#ffff00"; +export const DEFAULT_INF_ALPHA = 1; + +export const infAtom = atom<{ + active: boolean; + animationMode: AnimationMode; + color: { + r: number; + g: number; + b: number; + a: number; + }; +}>({ + active: false, + animationMode: "Blink", + color: { + r: 255, + g: 255, + b: 0, + a: DEFAULT_INF_ALPHA, + }, +}); diff --git a/tools/halidoscope/src/state/packet.ts b/tools/halidoscope/src/state/packet.ts new file mode 100644 index 000000000000..765e8fcaca2a --- /dev/null +++ b/tools/halidoscope/src/state/packet.ts @@ -0,0 +1,3 @@ +import { atom } from "jotai"; + +export const packetAtom = atom(0); diff --git a/tools/halidoscope/src/state/playback.ts b/tools/halidoscope/src/state/playback.ts new file mode 100644 index 000000000000..bf4879d92456 --- /dev/null +++ b/tools/halidoscope/src/state/playback.ts @@ -0,0 +1,6 @@ +import { atom } from "jotai"; + +/** Default packets replayed in each tick during trace playback. */ +export const DEFAULT_PLAYBACK_RATE = 10000; + +export const playbackRateAtom = atom(DEFAULT_PLAYBACK_RATE); diff --git a/tools/halidoscope/src/state/profile-metric.ts b/tools/halidoscope/src/state/profile-metric.ts new file mode 100644 index 000000000000..0d5e4a8bd81d --- /dev/null +++ b/tools/halidoscope/src/state/profile-metric.ts @@ -0,0 +1,16 @@ +import { atom } from "jotai"; + +export type ProfileMetric = + | "memory_peak" + | "memory_total" + | "stack_peak" + | "num_allocs"; + +export const PROFILE_METRICS = [ + "Memory Peak", + "Memory Total", + "Stack Peak", + "Num Allocs", +]; + +export const profileMetricAtom = atom("memory_total"); diff --git a/tools/halidoscope/src/state/render.ts b/tools/halidoscope/src/state/render.ts new file mode 100644 index 000000000000..46fbc1f41a9b --- /dev/null +++ b/tools/halidoscope/src/state/render.ts @@ -0,0 +1,18 @@ +import { atom } from "jotai"; + +export const RENDER_MODES = [ + "Grayscale", + "RGB", + "Store Frequency", + "Load Frequency", + "Redundant Stores", + "Reuse Distance", + "Thread Coverage", +] as const; +export type RenderMode = (typeof RENDER_MODES)[number]; +export type NormalizationMode = "Across Funcs" | "Per Func"; + +export const renderAtom = atom<{ + renderMode: RenderMode; + normalizationMode: NormalizationMode; +}>({ renderMode: "Grayscale", normalizationMode: "Across Funcs" }); diff --git a/tools/halidoscope/src/state/tabularData.ts b/tools/halidoscope/src/state/tabularData.ts new file mode 100644 index 000000000000..f8b21997a07c --- /dev/null +++ b/tools/halidoscope/src/state/tabularData.ts @@ -0,0 +1,11 @@ +import { atom } from "jotai"; + +export type Scale = "linear" | "log"; + +export const tabularDataAtom = atom<{ + tabularData: Uint32Array | null; + scale: Scale; +}>({ + tabularData: null, + scale: "linear", +}); diff --git a/tools/halidoscope/src/state/thread.ts b/tools/halidoscope/src/state/thread.ts new file mode 100644 index 000000000000..abb06670739e --- /dev/null +++ b/tools/halidoscope/src/state/thread.ts @@ -0,0 +1,8 @@ +import { atom } from "jotai"; + +export type ThreadOpMode = "Store" | "Load"; +export const NO_THREAD_INFO_SENTINEL_ID = "-1"; +export const threadAtom = atom<{ id: string; op: ThreadOpMode }>({ + id: NO_THREAD_INFO_SENTINEL_ID, + op: "Store", +}); diff --git a/tools/halidoscope/src/types/profile.ts b/tools/halidoscope/src/types/profile.ts new file mode 100644 index 000000000000..98583e744b9b --- /dev/null +++ b/tools/halidoscope/src/types/profile.ts @@ -0,0 +1,127 @@ +/** + * Represents per-Func profiling stats for a single run of a pipeline, as + * captured by Halide's sampling profiler. + */ +export interface ProfileFunc { + /** The name of the Func. */ + name: string; + /** The id of the parent Func this one is `compute_at`. `-1` if the Func is + * `compute_root`. + */ + parent: number; + /** + * The id of this Func's canonical entry. + * + * @remarks + * + * A Func can appear in the funcs array more than once (e.g., an unscheduled + * Func with an update definition reached from multiple callers); + * `canonical_id` is the id of the first such appearance, the shared key for + * rolling instances back up to a single Func. + */ + canonical_id: number; + /** + * A tag identifying what this entry represents. + * + * @remarks + * + * `0` = an ordinary Func, + * `1` = profiler overhead bookkeeping, + * `2` = thread-idle bookkeeping, + * `3` = malloc, + * `4` = free, + * `5` = `copy_to_host`, + * `6` = `copy_to_device`, + * `7` = a `hoist_storage` allocation entry (carries the memory columns for + * the buffer's lifetime, while the time/compute columns belong to a separate + * production entry). + */ + kind: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + /** + * For `copy_to_host`/`copy_to_device` entries (see {@link ProfileFunc.kind}), + * the `canonical_id` of the Func whose buffer is being copied. `-1` otherwise. + */ + buffer_func_id: number; + /** The total time spent evaluating this Func, in nanoseconds. */ + time_ns: number; + /** The current memory allocation of this Func, in bytes. */ + memory_current: number; + /** The peak memory allocation of this Func, in bytes. */ + memory_peak: number; + /** The total memory allocation of this Func, in bytes. */ + memory_total: number; + /** The peak stack allocation across this Func's threads, in bytes. */ + stack_peak: number; + /** + * The numerator of the average number of thread pool worker threads active + * while computing this Func. Divide by {@link ProfileFunc.active_threads_denominator} + * to get the average. + */ + active_threads_numerator: number; + /** + * The denominator of the average number of thread pool worker threads active + * while computing this Func. + */ + active_threads_denominator: number; + /** The total number of times heap storage for this Func was allocated. */ + num_allocs: number; +} + +/** + * Represents profiling stats for a single run of a pipeline, as captured by + * Halide's sampling profiler. + */ +export interface ProfilePipeline { + /** The name of the pipeline. */ + name: string; + /** The number of times this pipeline has been run, ever, since the last reset. */ + runs: number; + /** + * The number of pipeline runs that produced at least one profiler sample. + * + * @remarks + * + * Runs that completed in less than one sampler tick contribute to + * {@link ProfilePipeline.runs} (and to the per-Func counters) but not to + * per-Func time accumulation, so this is the correct denominator for time + * averages. + */ + billed_runs: number; + /** The total number of samples taken inside this pipeline, across all its runs. */ + samples: number; + /** The total number of memory allocations made by Funcs in this pipeline,across all runs. */ + num_allocs: number; + /** + * The time billed to Funcs in this pipeline run by the sampling thread, + * in nanoseconds. + */ + time_ns: number; + /** The current memory allocation of Funcs in this pipeline run, in bytes. */ + memory_current: number; + /** The peak memory allocation of Funcs in this pipeline run, in bytes. */ + memory_peak: number; + /** The total memory allocation of Funcs in this pipeline run, in bytes. */ + memory_total: number; + /** + * The numerator of the average number of thread pool worker threads doing + * useful work while computing this pipeline run. Divide by + * {@link ProfilePipeline.active_threads_denominator} to get the average. + */ + active_threads_numerator: number; + /** + * The denominator of the average number of thread pool worker threads doing + * useful work while computing this pipeline run. + */ + active_threads_denominator: number; + /** Per-Func profiling stats for this pipeline run. */ + funcs: ProfileFunc[]; +} + +/** + * Represents the complete profiling payload for a pipeline run, as returned by + * the `Pipeline::halidoscope` capture or from a pre-recorded JSON file. + */ +export interface Profile { + /** The profiled pipelines captured in this profile snapshot. */ + pipelines: ProfilePipeline[]; +} diff --git a/tools/halidoscope/src/types/trace.ts b/tools/halidoscope/src/types/trace.ts new file mode 100644 index 000000000000..3bdb81cbc01a --- /dev/null +++ b/tools/halidoscope/src/types/trace.ts @@ -0,0 +1,96 @@ +/** + * Represents the extent of a liveness range for a Func. + */ +export interface LivenessRange { + /** The packet index where a given Func is live. */ + start: number; + /** The packet index where a given Func is no longer live. */ + end: number; +} + +/** + * Represents top-level metadata for a Func. + */ +export interface FuncMeta extends Record { + /** The name of the Func. */ + name: string; + /** The width of the Func's buffer. */ + width: number; + /** The height of the Func's buffer. */ + height: number; + /** The number of channels in the Func's buffer. */ + channels: number; + /** The minimum coordinate observed along each logical dimension. */ + min_coords: number[]; + /** The maximum (exclusive) coordinate observed along each logical dimension. */ + max_coords: number[]; + /** The minimum value observed across all loads/stores for this Func. */ + min_value: number | null; + /** The maximum value observed across all loads/stores for this Func. */ + max_value: number | null; + /** The maximum number of stores observed at any single coordinate for this Func. */ + max_store_count: number; + /** The maximum number of loads observed at any single coordinate for this Func. */ + max_load_count: number; + /** + * The maximum number of redundant stores observed at any single coordinate for this Func. + * + * @remarks + * + * A store is redundant when the incoming value bit-matches the previously stored + * value at that coordinate and there are no intervening loads from it. + */ + max_redundant_store_count: number; + /** + * The maximum store-to-load reuse distance observed across all coordinates for this Func. + * + * @remarks + * + * Measured as the difference in global packet indices between a store and the + * next load from the same coordinate. + */ + max_reuse_distance: number; + /** The packet index range over which this Func's buffer is live in memory. */ + buffer_liveness: LivenessRange; + /** The packet index ranges during which this Func is being produced. */ + produce_ranges: LivenessRange[]; + /** The packet index ranges during which this Func is being consumed. */ + consume_ranges: LivenessRange[]; + /** The number of distinct threads that executed this Func. */ + thread_count: number; + /** The IDs of the distinct threads that executed this Func. */ + thread_ids: string[]; +} + +/** + * Represents trace-wide statistics aggregated across all Funcs. + */ +export interface StatsMeta { + /** The maximum store count observed at any coordinate across all Funcs. */ + global_max_store_count: number; + /** The maximum load count observed at any coordinate across all Funcs. */ + global_max_load_count: number; + /** The maximum redundant store count observed at any coordinate across all Funcs. */ + global_max_redundant_store_count: number; + /** The maximum store-to-load reuse distance observed across all Funcs. */ + global_max_reuse_distance: number; + /** The IDs of every distinct thread observed across all Funcs. */ + global_thread_ids: string[]; +} + +/** + * Represents the complete metadata payload for a trace, as returned by the `open_trace` command. + */ +export interface TraceMeta { + /** Metadata for each Func in the trace. */ + funcs: FuncMeta[]; + /** The total number of packets in the trace. */ + total_packets: number; + /** + * A map from each Func's name to the names of the Funcs it consumes (i.e. its producers in the + * pipeline's DAG). + */ + dag_edges: Record; + /** Trace-wide statistics aggregated across all Funcs. */ + stats: StatsMeta; +} diff --git a/tools/halidoscope/src/utils/api.ts b/tools/halidoscope/src/utils/api.ts new file mode 100644 index 000000000000..901b958c6ac3 --- /dev/null +++ b/tools/halidoscope/src/utils/api.ts @@ -0,0 +1,468 @@ +import { invoke } from "@tauri-apps/api/core"; + +import type { NormalizationMode } from "@/state/render"; +import type { ThreadOpMode } from "@/state/thread"; +import type { Profile } from "@/types/profile"; +import type { TraceMeta } from "@/types/trace"; + +/** + * Parse a `.hltrace` file and return the metadata needed to set up canvases and + * the scrub timeline. Replaces any previously loaded trace. + * + * @param path The path to the `.hltrace` file to open. + * @returns The parsed {@link TraceMeta} for the trace. + */ +export async function openTrace(path: string): Promise { + return invoke("open_trace", { path }); +} + +/** The unpacked payload returned by a `render_*` command. */ +export interface RenderFuncResponse { + /** The rendered RGBA8 tensor data for the Func's buffer. */ + tensorData: Uint8ClampedArray; + /** The RGBA8 overlay marking coordinates where a NaN was observed. */ + nanOverlayData: Uint8ClampedArray | null; + /** The RGBA8 overlay marking coordinates where an Inf was observed. */ + infOverlayData: Uint8ClampedArray | null; + /** + * The per-coordinate tabular data backing histograms, or null if not + * requested. + */ + tabularData: Uint32Array | null; +} + +/** Shared parameters accepted by every `render_*` command. */ +export interface RenderFuncParams { + /** The name of the Func to render. */ + func: string; + /** The global packet index to render up to. */ + globalIndex: number; + /** Whether pixel values are normalized against all Funcs or just this one. */ + normalizationMode: NormalizationMode; + /** The width of the Func's buffer. */ + width: number; + /** The height of the Func's buffer. */ + height: number; + /** + * Whether to compute and return per-coordinate tabular data alongside the + * rendered pixels. + */ + includeTabularData: boolean; + /** + * The overlay color to apply at coordinates where a NaN was observed, + * if `active`. + */ + includeNan: { + active: boolean; + r: number; + g: number; + b: number; + a: number; + }; + /** + * The overlay color to apply at coordinates where an Inf was observed, + * if `active`. + */ + includeInf: { + active: boolean; + r: number; + g: number; + b: number; + a: number; + }; +} + +/** + * Expands a 1-bit-per-pixel row-major overlay mask (MSB-first within each + * byte, as packed by `pack_mask` in `render.rs`) into a dense RGBA8 buffer, + * painting set pixels with `color` and leaving unset pixels fully transparent. + * + * @param mask The packed bitmask, `ceil(width * height / 8)` bytes. + * @param width The width of the buffer the mask covers. + * @param height The height of the buffer the mask covers. + * @param color The color to paint set pixels with. + * @returns A `width * height * 4`-byte RGBA8 buffer. + */ +function expandMask( + mask: Uint8Array, + width: number, + height: number, + color: { + r: number; + g: number; + b: number; + a: number; + }, +): Uint8ClampedArray { + const out = new Uint8ClampedArray(width * height * 4); + const alpha = Math.round(Math.min(1, Math.max(0, color.a)) * 255); + + for (let i = 0; i < width * height; i++) { + const bit = (mask[i >> 3] >> (7 - (i & 7))) & 1; + + if (bit) { + const o = i * 4; + out[o] = color.r; + out[o + 1] = color.g; + out[o + 2] = color.b; + out[o + 3] = alpha; + } + } + + return out; +} + +/** + * Splits the ArrayBuffer returned by a render command into the tensor data, + * NaN/Inf overlays, and the (optionally returned) tabular data. The NaN/Inf + * overlay planes are 1-bit-per-pixel masks (see `pack_mask` in `render.rs`) + * expanded here into RGBA8 using the same color the caller requested them in, + * since every set pixel in a given overlay shares that one color. + * + * @param buffer The buffer containing tensor, tabular, and overlay data. + * @param width The width of the buffer. + * @param height The height of the buffer. + * @param includeNan The NaN overlay color, or `null` if not requested. + * @param includeInf The Inf overlay color, or `null` if not requested. + * @param includeTabularData A flag indicating whether or not to expect tabular + * data in the buffer payload. + * @returns A {@link RenderFuncResponse}. + */ +function splitRenderBuffer({ + buffer, + width, + height, + includeNan, + includeInf, + includeTabularData, +}: { + buffer: ArrayBuffer; + width: number; + height: number; + includeNan: RenderFuncParams["includeNan"] | null; + includeInf: RenderFuncParams["includeInf"] | null; + includeTabularData: boolean; +}): RenderFuncResponse { + const pixelByteLength = width * height * 4; + const maskByteLength = Math.ceil((width * height) / 8); + const overlayBytes = + (includeNan ? maskByteLength : 0) + (includeInf ? maskByteLength : 0); + const tabularByteLength = buffer.byteLength - pixelByteLength - overlayBytes; + + const nanMaskOffset = pixelByteLength; + const infMaskOffset = nanMaskOffset + (includeNan ? maskByteLength : 0); + const tabularOffset = infMaskOffset + (includeInf ? maskByteLength : 0); + + return { + tensorData: new Uint8ClampedArray(buffer, 0, pixelByteLength), + nanOverlayData: includeNan + ? expandMask( + new Uint8Array(buffer, nanMaskOffset, maskByteLength), + width, + height, + includeNan, + ) + : null, + infOverlayData: includeInf + ? expandMask( + new Uint8Array(buffer, infMaskOffset, maskByteLength), + width, + height, + includeInf, + ) + : null, + tabularData: includeTabularData + ? new Uint32Array(buffer, tabularOffset, tabularByteLength / 4) + : null, + }; +} + +/** + * Render a Func as a grayscale image at a given packet index. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderGrayscale({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_grayscale", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Render a Func as an RGB image at a given packet index. Channels 0/1/2 map to + * R/G/B. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderRgb({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_rgb", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Render a heatmap of store counts for a Func at a given packet index. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderStoreFrequency({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_store_frequency", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Render a heatmap of load counts for a Func at a given packet index. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderLoadFrequency({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_load_frequency", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Render a heatmap of redundant store counts for a Func at a given packet + * index. + * + * @remarks + * + * A store is redundant when it writes the same value to a location that already + * holds that value _and_ no intervening load has read that value. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderRedundantStores({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_redundant_stores", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Render a heatmap of maximum store-to-load reuse distances for a Func at a + * given packet index. + * + * @remarks + * + * Reuse distance is the number of packets elapsed between a store and the next + * load from the same (x, y, channel). In the case of input buffers, it is the + * distance from the first load to the last load from that buffer. + * + * @param params The {@link RenderFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderReuseDistance({ + func, + globalIndex, + normalizationMode, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderFuncParams): Promise { + const buffer = await invoke("render_reuse_distance", { + func, + globalIndex, + normalizationMode, + includeTabularData, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** Parameters accepted by the `render_thread` command. */ +export interface RenderThreadFuncParams extends RenderFuncParams { + /** + * Whether to render the store or load operations attributed to `threadId`. + */ + threadOpMode: ThreadOpMode; + /** The ID of the thread to render coverage for. */ + threadId: string; +} + +/** + * Render a heatmap of the coordinates stored to or loaded from by a single + * thread for a Func at a given packet index. + * + * @param params The {@link RenderThreadFuncParams} describing what to render. + * @returns The {@link RenderFuncResponse} split out from the backend's raw + * buffer. + */ +export async function renderThread({ + func, + globalIndex, + normalizationMode, + threadOpMode, + threadId, + width, + height, + includeTabularData, + includeNan, + includeInf, +}: RenderThreadFuncParams): Promise { + const buffer = await invoke("render_thread", { + func, + globalIndex, + normalizationMode, + opMode: threadOpMode, + threadId, + includeNan: includeNan.active, + includeInf: includeInf.active, + }); + + return splitRenderBuffer({ + buffer, + width, + height, + includeNan: includeNan.active ? includeNan : null, + includeInf: includeInf.active ? includeInf : null, + includeTabularData, + }); +} + +/** + * Parse a Halide profiler output file and return its contents. + * + * @param path The path to the profiler output file to open. + * @returns The parsed {@link Profile}. + */ +export async function openProfile(path: string): Promise { + return invoke("open_profile", { path }); +} diff --git a/tools/halidoscope/src/utils/formatters.ts b/tools/halidoscope/src/utils/formatters.ts new file mode 100644 index 000000000000..33365fee4d8d --- /dev/null +++ b/tools/halidoscope/src/utils/formatters.ts @@ -0,0 +1,24 @@ +import * as d3 from "d3"; + +export type ByteUnit = "B" | "KB" | "MB" | "GB"; + +const numberStringRegex = /^([\d.]+)\s*(.*)$/; + +/** + * Format a numeric value as a byte string (e.g., 10KB, 1.5MB). + * + * @param bytes The count of bytes. + * @returns A formatted byte string. + */ +export function formatBytes(bytes: number): { + value: number; + unit: ByteUnit; +} { + const formatted = d3.format(".2s")(bytes).replace("k", "K").concat("B"); + const result = formatted.match(numberStringRegex); + + return { + value: Number(result?.[1]), + unit: (result?.[2] as ByteUnit) ?? "B", + }; +} diff --git a/tools/halidoscope/src/utils/graph.ts b/tools/halidoscope/src/utils/graph.ts new file mode 100644 index 000000000000..e60bf280d8d4 --- /dev/null +++ b/tools/halidoscope/src/utils/graph.ts @@ -0,0 +1,116 @@ +import Dagre from "@dagrejs/dagre"; +import type { Node, Edge } from "@xyflow/react"; + +import type { FuncMeta } from "@/types/trace"; + +/** Represents the types of permitted nodes for the @xyflow/react canvas. */ +type NodeTypes = "funcNode"; + +/** + * Build xyflow nodes from the backend's funcs payload. + * + * @param funcs The funcs payload from the backend. + * @param type The node type to assign to each node. + * @returns An array of nodes formatted for use with @xyflow/react. + */ +export function buildNodes( + funcs: Record, + type: NodeTypes, +): Node[] { + return Object.entries(funcs).map(([name, stats]) => { + return { + id: name, + type, + position: { + x: 0, + y: 0, + }, + data: stats, + style: { + width: stats.width, + height: stats.height, + }, + }; + }); +} + +/** Represents the types of permitted edges for the @xyflow/react canvas. */ +type EdgeTypes = "funcEdge"; + +/** + * Build xyflow edges from the backend's dag_edges payload, which maps Halide + * consumers to their producers. + * + * @param dagEdges The dag_edges payload from the backend. + * @param type The edge type to assign to each edge. + * @returns An array of edges formatted for use with @xyflow/react. + */ +export function buildEdges( + dagEdges: Record, + type: EdgeTypes, +): Edge[] { + const edges: { + id: string; + source: string; + target: string; + type: EdgeTypes; + }[] = []; + + for (const [producer, consumers] of Object.entries(dagEdges)) { + for (const consumer of consumers) { + edges.push({ + id: `${producer}-${consumer}`, + type, + source: producer, + target: consumer, + }); + } + } + + return edges; +} + +/** + * Lay out the Halide pipeline as a DAG with Dagre and return nodes and edges + * augmented with positional information. + * + * @param nodes The set of Halide funcs in the pipeline. + * @param edges The edges representing dataflow between Halide funcs. + */ +export function getLayoutedElements( + nodes: Node[], + edges: Edge[], +): { nodes: Node[]; edges: Edge[] } { + const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); + g.setGraph({ rankdir: "LR", nodesep: 60, ranksep: 80 }); + + edges.forEach((edge) => g.setEdge(edge.source, edge.target)); + nodes.forEach((node) => { + const width = + node.measured?.width ?? (node.style?.width as number | undefined) ?? 150; + const height = + node.measured?.height ?? (node.style?.height as number | undefined) ?? 50; + g.setNode(node.id, { ...node, width, height }); + }); + + Dagre.layout(g); + + return { + nodes: nodes.map((node) => { + const position = g.node(node.id); + const width = + node.measured?.width ?? + (node.style?.width as number | undefined) ?? + 150; + const height = + node.measured?.height ?? + (node.style?.height as number | undefined) ?? + 50; + const x = position.x - width / 2; + const y = position.y - height / 2; + + return { ...node, position: { x, y } }; + }), + edges, + }; +} diff --git a/tools/halidoscope/src/utils/liveness.ts b/tools/halidoscope/src/utils/liveness.ts new file mode 100644 index 000000000000..f3a664894881 --- /dev/null +++ b/tools/halidoscope/src/utils/liveness.ts @@ -0,0 +1,65 @@ +import type { FuncMeta } from "@/types/trace"; + +/** + * Determine whether a Func's buffer is live in memory at a given point in the + * åtrace. + * + * @param func The Func metadata to check. + * @param globalIndex The global packet index to check liveness at. + * @returns Whether the Func's buffer is live at `globalIndex`. + */ +export function isFuncBufferLive(func: FuncMeta, globalIndex: number) { + return ( + func.buffer_liveness.start <= globalIndex && + globalIndex <= func.buffer_liveness.end + ); +} + +/** + * Determine whether a Func is being consumed at a given point in the trace. + * + * @param func The Func metadata to check. + * @param globalIndex The global packet index to check against. + * @returns Whether `globalIndex` falls within one of the Func's consume ranges. + */ +export function isFuncConsuming(func: FuncMeta, globalIndex: number) { + return func.consume_ranges.some( + (range) => range.start <= globalIndex && globalIndex <= range.end, + ); +} + +/** + * Determine whether a Func is being produced at a given point in the trace. + * + * @param func The Func metadata to check. + * @param globalIndex The global packet index to check against. + * @returns Whether `globalIndex` falls within one of the Func's produce ranges. + */ +export function isFuncProducing(func: FuncMeta, globalIndex: number) { + return func.produce_ranges.some( + (range) => range.start <= globalIndex && globalIndex <= range.end, + ); +} + +/** + * Determine whether the dataflow edge between a producer and consumer Func is + * live at a given point in the trace. + * + * @param funcs A map from Func name to its metadata. + * @param source The name of the producer Func. + * @param target The name of the consumer Func. + * @param globalIndex The global packet index to check against. + * @returns Whether `source` is producing and `target` is consuming at + * `globalIndex`. + */ +export function isEdgeLive( + funcs: Record, + source: string, + target: string, + globalIndex: number, +) { + return ( + isFuncProducing(funcs[source], globalIndex) && + isFuncConsuming(funcs[target], globalIndex) + ); +} diff --git a/tools/halidoscope/src/vite-env.d.ts b/tools/halidoscope/src/vite-env.d.ts new file mode 100644 index 000000000000..11f02fe2a006 --- /dev/null +++ b/tools/halidoscope/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tools/halidoscope/tsconfig.json b/tools/halidoscope/tsconfig.json new file mode 100644 index 000000000000..ed8feb33ec8d --- /dev/null +++ b/tools/halidoscope/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/tools/halidoscope/tsconfig.node.json b/tools/halidoscope/tsconfig.node.json new file mode 100644 index 000000000000..b5a343184303 --- /dev/null +++ b/tools/halidoscope/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": [ + "vite.config.ts" + ] +} diff --git a/tools/halidoscope/vite.config.ts b/tools/halidoscope/vite.config.ts new file mode 100644 index 000000000000..66b2910e5716 --- /dev/null +++ b/tools/halidoscope/vite.config.ts @@ -0,0 +1,35 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [react(), tailwindcss()], + resolve: { + tsconfigPaths: true, + }, + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +}));