Merge branch 'main' into fix-broken-lsp-installations

Julia created

Change summary

.cargo/config.toml                                                              |    2 
.config/nextest.toml                                                            |    6 
.github/workflows/ci.yml                                                        |    3 
.gitignore                                                                      |    2 
Cargo.lock                                                                      |  145 
Cargo.toml                                                                      |    4 
assets/icons/assist_15.svg                                                      |    0 
assets/icons/hamburger_15.svg                                                   |    3 
assets/icons/quote_15.svg                                                       |    0 
assets/icons/radix/accessibility.svg                                            |    4 
assets/icons/radix/activity-log.svg                                             |    4 
assets/icons/radix/align-baseline.svg                                           |    4 
assets/icons/radix/align-bottom.svg                                             |    8 
assets/icons/radix/align-center-horizontally.svg                                |    8 
assets/icons/radix/align-center-vertically.svg                                  |    8 
assets/icons/radix/align-center.svg                                             |    8 
assets/icons/radix/align-end.svg                                                |    8 
assets/icons/radix/align-horizontal-centers.svg                                 |    8 
assets/icons/radix/align-left.svg                                               |    8 
assets/icons/radix/align-right.svg                                              |    8 
assets/icons/radix/align-start.svg                                              |    8 
assets/icons/radix/align-stretch.svg                                            |    8 
assets/icons/radix/align-top.svg                                                |    8 
assets/icons/radix/align-vertical-centers.svg                                   |    8 
assets/icons/radix/all-sides.svg                                                |    8 
assets/icons/radix/angle.svg                                                    |    8 
assets/icons/radix/archive.svg                                                  |    8 
assets/icons/radix/arrow-bottom-left.svg                                        |    8 
assets/icons/radix/arrow-bottom-right.svg                                       |    8 
assets/icons/radix/arrow-down.svg                                               |    8 
assets/icons/radix/arrow-left.svg                                               |    8 
assets/icons/radix/arrow-right.svg                                              |    8 
assets/icons/radix/arrow-top-left.svg                                           |    8 
assets/icons/radix/arrow-top-right.svg                                          |    8 
assets/icons/radix/arrow-up.svg                                                 |    8 
assets/icons/radix/aspect-ratio.svg                                             |    8 
assets/icons/radix/avatar.svg                                                   |    4 
assets/icons/radix/backpack.svg                                                 |    8 
assets/icons/radix/badge.svg                                                    |    8 
assets/icons/radix/bar-chart.svg                                                |    8 
assets/icons/radix/bell.svg                                                     |    4 
assets/icons/radix/blending-mode.svg                                            |    8 
assets/icons/radix/bookmark-filled.svg                                          |    8 
assets/icons/radix/bookmark.svg                                                 |    8 
assets/icons/radix/border-all.svg                                               |   17 
assets/icons/radix/border-bottom.svg                                            |   29 
assets/icons/radix/border-dashed.svg                                            |    8 
assets/icons/radix/border-dotted.svg                                            |    8 
assets/icons/radix/border-left.svg                                              |   29 
assets/icons/radix/border-none.svg                                              |   35 
assets/icons/radix/border-right.svg                                             |   29 
assets/icons/radix/border-solid.svg                                             |    8 
assets/icons/radix/border-split.svg                                             |   21 
assets/icons/radix/border-style.svg                                             |    4 
assets/icons/radix/border-top.svg                                               |   29 
assets/icons/radix/border-width.svg                                             |    8 
assets/icons/radix/box-model.svg                                                |    8 
assets/icons/radix/box.svg                                                      |    8 
assets/icons/radix/button.svg                                                   |    8 
assets/icons/radix/calendar.svg                                                 |    4 
assets/icons/radix/camera.svg                                                   |    8 
assets/icons/radix/card-stack-minus.svg                                         |    8 
assets/icons/radix/card-stack-plus.svg                                          |    8 
assets/icons/radix/card-stack.svg                                               |    8 
assets/icons/radix/caret-down.svg                                               |    8 
assets/icons/radix/caret-left.svg                                               |    8 
assets/icons/radix/caret-right.svg                                              |    8 
assets/icons/radix/caret-sort.svg                                               |    8 
assets/icons/radix/caret-up.svg                                                 |    8 
assets/icons/radix/chat-bubble.svg                                              |    8 
assets/icons/radix/check-circled.svg                                            |    8 
assets/icons/radix/check.svg                                                    |    8 
assets/icons/radix/checkbox.svg                                                 |    8 
assets/icons/radix/chevron-down.svg                                             |    8 
assets/icons/radix/chevron-left.svg                                             |    8 
assets/icons/radix/chevron-right.svg                                            |    8 
assets/icons/radix/chevron-up.svg                                               |    8 
assets/icons/radix/circle-backslash.svg                                         |    8 
assets/icons/radix/circle.svg                                                   |    8 
assets/icons/radix/clipboard-copy.svg                                           |    4 
assets/icons/radix/clipboard.svg                                                |    8 
assets/icons/radix/clock.svg                                                    |    8 
assets/icons/radix/code.svg                                                     |    8 
assets/icons/radix/codesandbox-logo.svg                                         |    4 
assets/icons/radix/color-wheel.svg                                              |    8 
assets/icons/radix/column-spacing.svg                                           |    4 
assets/icons/radix/columns.svg                                                  |    8 
assets/icons/radix/commit.svg                                                   |    8 
assets/icons/radix/component-1.svg                                              |    4 
assets/icons/radix/component-2.svg                                              |    4 
assets/icons/radix/component-boolean.svg                                        |    8 
assets/icons/radix/component-instance.svg                                       |    8 
assets/icons/radix/component-none.svg                                           |    8 
assets/icons/radix/component-placeholder.svg                                    |    4 
assets/icons/radix/container.svg                                                |    4 
assets/icons/radix/cookie.svg                                                   |    4 
assets/icons/radix/copy.svg                                                     |    8 
assets/icons/radix/corner-bottom-left.svg                                       |    8 
assets/icons/radix/corner-bottom-right.svg                                      |    8 
assets/icons/radix/corner-top-left.svg                                          |    8 
assets/icons/radix/corner-top-right.svg                                         |    8 
assets/icons/radix/corners.svg                                                  |    4 
assets/icons/radix/countdown-timer.svg                                          |    8 
assets/icons/radix/counter-clockwise-clock.svg                                  |    8 
assets/icons/radix/crop.svg                                                     |    8 
assets/icons/radix/cross-1.svg                                                  |    8 
assets/icons/radix/cross-2.svg                                                  |    8 
assets/icons/radix/cross-circled.svg                                            |    8 
assets/icons/radix/crosshair-1.svg                                              |    8 
assets/icons/radix/crosshair-2.svg                                              |    8 
assets/icons/radix/crumpled-paper.svg                                           |    4 
assets/icons/radix/cube.svg                                                     |    8 
assets/icons/radix/cursor-arrow.svg                                             |    8 
assets/icons/radix/cursor-text.svg                                              |    4 
assets/icons/radix/dash.svg                                                     |    8 
assets/icons/radix/dashboard.svg                                                |    4 
assets/icons/radix/desktop-mute.svg                                             |    4 
assets/icons/radix/desktop.svg                                                  |    8 
assets/icons/radix/dimensions.svg                                               |    4 
assets/icons/radix/disc.svg                                                     |    8 
assets/icons/radix/discord-logo.svg                                             |    5 
assets/icons/radix/divider-horizontal.svg                                       |    8 
assets/icons/radix/divider-vertical.svg                                         |    8 
assets/icons/radix/dot-filled.svg                                               |    6 
assets/icons/radix/dot-solid.svg                                                |    6 
assets/icons/radix/dot.svg                                                      |    8 
assets/icons/radix/dots-horizontal.svg                                          |    8 
assets/icons/radix/dots-vertical.svg                                            |    8 
assets/icons/radix/double-arrow-down.svg                                        |    8 
assets/icons/radix/double-arrow-left.svg                                        |    8 
assets/icons/radix/double-arrow-right.svg                                       |    8 
assets/icons/radix/double-arrow-up.svg                                          |    8 
assets/icons/radix/download.svg                                                 |    8 
assets/icons/radix/drag-handle-dots-1.svg                                       |   26 
assets/icons/radix/drag-handle-dots-2.svg                                       |    4 
assets/icons/radix/drag-handle-horizontal.svg                                   |    8 
assets/icons/radix/drag-handle-vertical.svg                                     |    8 
assets/icons/radix/drawing-pin-filled.svg                                       |   14 
assets/icons/radix/drawing-pin-solid.svg                                        |   14 
assets/icons/radix/drawing-pin.svg                                              |    8 
assets/icons/radix/dropdown-menu.svg                                            |    8 
assets/icons/radix/enter-full-screen.svg                                        |    8 
assets/icons/radix/enter.svg                                                    |    8 
assets/icons/radix/envelope-closed.svg                                          |    8 
assets/icons/radix/envelope-open.svg                                            |    8 
assets/icons/radix/eraser.svg                                                   |    8 
assets/icons/radix/exclamation-triangle.svg                                     |    8 
assets/icons/radix/exit-full-screen.svg                                         |    8 
assets/icons/radix/exit.svg                                                     |    8 
assets/icons/radix/external-link.svg                                            |    8 
assets/icons/radix/eye-closed.svg                                               |    4 
assets/icons/radix/eye-none.svg                                                 |    8 
assets/icons/radix/eye-open.svg                                                 |    8 
assets/icons/radix/face.svg                                                     |    4 
assets/icons/radix/figma-logo.svg                                               |    4 
assets/icons/radix/file-minus.svg                                               |    8 
assets/icons/radix/file-plus.svg                                                |    8 
assets/icons/radix/file-text.svg                                                |    8 
assets/icons/radix/file.svg                                                     |    8 
assets/icons/radix/font-bold.svg                                                |    6 
assets/icons/radix/font-family.svg                                              |    6 
assets/icons/radix/font-italic.svg                                              |    8 
assets/icons/radix/font-roman.svg                                               |    8 
assets/icons/radix/font-size.svg                                                |    4 
assets/icons/radix/font-style.svg                                               |    4 
assets/icons/radix/frame.svg                                                    |    8 
assets/icons/radix/framer-logo.svg                                              |    8 
assets/icons/radix/gear.svg                                                     |    4 
assets/icons/radix/github-logo.svg                                              |    4 
assets/icons/radix/globe.svg                                                    |   26 
assets/icons/radix/grid.svg                                                     |    8 
assets/icons/radix/group.svg                                                    |    4 
assets/icons/radix/half-1.svg                                                   |    8 
assets/icons/radix/half-2.svg                                                   |    8 
assets/icons/radix/hamburger-menu.svg                                           |    8 
assets/icons/radix/hand.svg                                                     |    4 
assets/icons/radix/heading.svg                                                  |    8 
assets/icons/radix/heart-filled.svg                                             |    8 
assets/icons/radix/heart.svg                                                    |    4 
assets/icons/radix/height.svg                                                   |    8 
assets/icons/radix/hobby-knife.svg                                              |    8 
assets/icons/radix/home.svg                                                     |    8 
assets/icons/radix/iconjar-logo.svg                                             |    4 
assets/icons/radix/id-card.svg                                                  |    8 
assets/icons/radix/image.svg                                                    |    8 
assets/icons/radix/info-circled.svg                                             |    8 
assets/icons/radix/inner-shadow.svg                                             |   78 
assets/icons/radix/input.svg                                                    |    4 
assets/icons/radix/instagram-logo.svg                                           |    2 
assets/icons/radix/justify-center.svg                                           |    8 
assets/icons/radix/justify-end.svg                                              |    8 
assets/icons/radix/justify-start.svg                                            |    8 
assets/icons/radix/justify-stretch.svg                                          |    8 
assets/icons/radix/keyboard.svg                                                 |    7 
assets/icons/radix/lap-timer.svg                                                |    8 
assets/icons/radix/laptop.svg                                                   |    8 
assets/icons/radix/layers.svg                                                   |    4 
assets/icons/radix/layout.svg                                                   |    8 
assets/icons/radix/letter-case-capitalize.svg                                   |    4 
assets/icons/radix/letter-case-lowercase.svg                                    |    4 
assets/icons/radix/letter-case-toggle.svg                                       |    4 
assets/icons/radix/letter-case-uppercase.svg                                    |    8 
assets/icons/radix/letter-spacing.svg                                           |    4 
assets/icons/radix/lightning-bolt.svg                                           |    8 
assets/icons/radix/line-height.svg                                              |    4 
assets/icons/radix/link-1.svg                                                   |    4 
assets/icons/radix/link-2.svg                                                   |    4 
assets/icons/radix/link-break-1.svg                                             |    4 
assets/icons/radix/link-break-2.svg                                             |    4 
assets/icons/radix/link-none-1.svg                                              |    4 
assets/icons/radix/link-none-2.svg                                              |    4 
assets/icons/radix/linkedin-logo.svg                                            |    8 
assets/icons/radix/list-bullet.svg                                              |    8 
assets/icons/radix/lock-closed.svg                                              |    8 
assets/icons/radix/lock-open-1.svg                                              |    8 
assets/icons/radix/lock-open-2.svg                                              |    8 
assets/icons/radix/loop.svg                                                     |    8 
assets/icons/radix/magic-wand.svg                                               |    4 
assets/icons/radix/magnifying-glass.svg                                         |    8 
assets/icons/radix/margin.svg                                                   |    4 
assets/icons/radix/mask-off.svg                                                 |    8 
assets/icons/radix/mask-on.svg                                                  |    8 
assets/icons/radix/mic-mute.svg                                                 |    1 
assets/icons/radix/mic.svg                                                      |    1 
assets/icons/radix/minus-circled.svg                                            |    8 
assets/icons/radix/minus.svg                                                    |    8 
assets/icons/radix/mix.svg                                                      |    4 
assets/icons/radix/mixer-horizontal.svg                                         |    4 
assets/icons/radix/mixer-vertical.svg                                           |    4 
assets/icons/radix/mobile.svg                                                   |    8 
assets/icons/radix/modulz-logo.svg                                              |    8 
assets/icons/radix/moon.svg                                                     |    4 
assets/icons/radix/move.svg                                                     |    4 
assets/icons/radix/notion-logo.svg                                              |    2 
assets/icons/radix/opacity.svg                                                  |    8 
assets/icons/radix/open-in-new-window.svg                                       |   10 
assets/icons/radix/outer-shadow.svg                                             |   43 
assets/icons/radix/overline.svg                                                 |    8 
assets/icons/radix/padding.svg                                                  |    4 
assets/icons/radix/paper-plane.svg                                              |    8 
assets/icons/radix/pause.svg                                                    |    8 
assets/icons/radix/pencil-1.svg                                                 |    8 
assets/icons/radix/pencil-2.svg                                                 |    4 
assets/icons/radix/person.svg                                                   |    8 
assets/icons/radix/pie-chart.svg                                                |    8 
assets/icons/radix/pilcrow.svg                                                  |    8 
assets/icons/radix/pin-bottom.svg                                               |    8 
assets/icons/radix/pin-left.svg                                                 |    8 
assets/icons/radix/pin-right.svg                                                |    8 
assets/icons/radix/pin-top.svg                                                  |    8 
assets/icons/radix/play.svg                                                     |    8 
assets/icons/radix/plus-circled.svg                                             |    8 
assets/icons/radix/plus.svg                                                     |    8 
assets/icons/radix/question-mark-circled.svg                                    |    4 
assets/icons/radix/question-mark.svg                                            |    8 
assets/icons/radix/quote.svg                                                    |    4 
assets/icons/radix/radiobutton.svg                                              |    8 
assets/icons/radix/reader.svg                                                   |    4 
assets/icons/radix/reload.svg                                                   |    8 
assets/icons/radix/reset.svg                                                    |    8 
assets/icons/radix/resume.svg                                                   |    8 
assets/icons/radix/rocket.svg                                                   |    4 
assets/icons/radix/rotate-counter-clockwise.svg                                 |    8 
assets/icons/radix/row-spacing.svg                                              |    4 
assets/icons/radix/rows.svg                                                     |    8 
assets/icons/radix/ruler-horizontal.svg                                         |    8 
assets/icons/radix/ruler-square.svg                                             |    4 
assets/icons/radix/scissors.svg                                                 |    4 
assets/icons/radix/section.svg                                                  |    4 
assets/icons/radix/sewing-pin-filled.svg                                        |    8 
assets/icons/radix/sewing-pin-solid.svg                                         |    8 
assets/icons/radix/sewing-pin.svg                                               |    8 
assets/icons/radix/shadow-inner.svg                                             |   78 
assets/icons/radix/shadow-none.svg                                              |   78 
assets/icons/radix/shadow-outer.svg                                             |   43 
assets/icons/radix/shadow.svg                                                   |   78 
assets/icons/radix/share-1.svg                                                  |    4 
assets/icons/radix/share-2.svg                                                  |    4 
assets/icons/radix/shuffle.svg                                                  |    4 
assets/icons/radix/size.svg                                                     |    8 
assets/icons/radix/sketch-logo.svg                                              |    4 
assets/icons/radix/slash.svg                                                    |    8 
assets/icons/radix/slider.svg                                                   |    8 
assets/icons/radix/space-between-horizontally.svg                               |    8 
assets/icons/radix/space-between-vertically.svg                                 |    8 
assets/icons/radix/space-evenly-horizontally.svg                                |    4 
assets/icons/radix/space-evenly-vertically.svg                                  |    8 
assets/icons/radix/speaker-loud.svg                                             |    4 
assets/icons/radix/speaker-moderate.svg                                         |    8 
assets/icons/radix/speaker-off.svg                                              |    8 
assets/icons/radix/speaker-quiet.svg                                            |    8 
assets/icons/radix/square.svg                                                   |    8 
assets/icons/radix/stack.svg                                                    |    8 
assets/icons/radix/star-filled.svg                                              |    6 
assets/icons/radix/star.svg                                                     |    4 
assets/icons/radix/stitches-logo.svg                                            |    4 
assets/icons/radix/stop.svg                                                     |    8 
assets/icons/radix/stopwatch.svg                                                |    8 
assets/icons/radix/stretch-horizontally.svg                                     |    8 
assets/icons/radix/stretch-vertically.svg                                       |    8 
assets/icons/radix/strikethrough.svg                                            |    8 
assets/icons/radix/sun.svg                                                      |    4 
assets/icons/radix/switch.svg                                                   |    8 
assets/icons/radix/symbol.svg                                                   |    4 
assets/icons/radix/table.svg                                                    |    8 
assets/icons/radix/target.svg                                                   |    4 
assets/icons/radix/text-align-bottom.svg                                        |    4 
assets/icons/radix/text-align-center.svg                                        |    8 
assets/icons/radix/text-align-justify.svg                                       |    8 
assets/icons/radix/text-align-left.svg                                          |    8 
assets/icons/radix/text-align-middle.svg                                        |    4 
assets/icons/radix/text-align-right.svg                                         |    8 
assets/icons/radix/text-align-top.svg                                           |    4 
assets/icons/radix/text-none.svg                                                |    8 
assets/icons/radix/text.svg                                                     |    8 
assets/icons/radix/thick-arrow-down.svg                                         |    8 
assets/icons/radix/thick-arrow-left.svg                                         |    8 
assets/icons/radix/thick-arrow-right.svg                                        |    8 
assets/icons/radix/thick-arrow-up.svg                                           |    8 
assets/icons/radix/timer.svg                                                    |    8 
assets/icons/radix/tokens.svg                                                   |    8 
assets/icons/radix/track-next.svg                                               |    8 
assets/icons/radix/track-previous.svg                                           |    8 
assets/icons/radix/transform.svg                                                |    4 
assets/icons/radix/transparency-grid.svg                                        |    9 
assets/icons/radix/trash.svg                                                    |    8 
assets/icons/radix/triangle-down.svg                                            |    3 
assets/icons/radix/triangle-left.svg                                            |    3 
assets/icons/radix/triangle-right.svg                                           |    3 
assets/icons/radix/triangle-up.svg                                              |    3 
assets/icons/radix/twitter-logo.svg                                             |    4 
assets/icons/radix/underline.svg                                                |    8 
assets/icons/radix/update.svg                                                   |    4 
assets/icons/radix/upload.svg                                                   |    8 
assets/icons/radix/value-none.svg                                               |    8 
assets/icons/radix/value.svg                                                    |    8 
assets/icons/radix/vercel-logo.svg                                              |    8 
assets/icons/radix/video.svg                                                    |    4 
assets/icons/radix/view-grid.svg                                                |    8 
assets/icons/radix/view-horizontal.svg                                          |    8 
assets/icons/radix/view-none.svg                                                |    8 
assets/icons/radix/view-vertical.svg                                            |    8 
assets/icons/radix/width.svg                                                    |    8 
assets/icons/radix/zoom-in.svg                                                  |    8 
assets/icons/radix/zoom-out.svg                                                 |    8 
assets/icons/split_message_15.svg                                               |    0 
assets/keymaps/default.json                                                     |   18 
assets/keymaps/vim.json                                                         |   60 
assets/settings/default.json                                                    |   54 
crates/activity_indicator/src/activity_indicator.rs                             |    2 
crates/ai/Cargo.toml                                                            |    4 
crates/ai/src/ai.rs                                                             |   92 
crates/ai/src/assistant.rs                                                      |  755 
crates/auto_update/src/update_notification.rs                                   |    4 
crates/breadcrumbs/src/breadcrumbs.rs                                           |    2 
crates/call/src/participant.rs                                                  |    6 
crates/call/src/room.rs                                                         |  386 
crates/client/src/telemetry.rs                                                  |   42 
crates/collab/Cargo.toml                                                        |    2 
crates/collab/migrations.sqlite/20221109000000_test_schema.sql                  |    1 
crates/collab/migrations/20230616134535_add_is_external_to_worktree_entries.sql |    2 
crates/collab/src/db.rs                                                         |    3 
crates/collab/src/db/worktree_entry.rs                                          |    1 
crates/collab/src/rpc.rs                                                        |    1 
crates/collab/src/tests/integration_tests.rs                                    |   27 
crates/collab_ui/Cargo.toml                                                     |    2 
crates/collab_ui/src/collab_titlebar_item.rs                                    |  416 
crates/collab_ui/src/collab_ui.rs                                               |   40 
crates/collab_ui/src/contact_finder.rs                                          |    3 
crates/collab_ui/src/contact_list.rs                                            |   45 
crates/collab_ui/src/notifications.rs                                           |    4 
crates/command_palette/src/command_palette.rs                                   |    4 
crates/context_menu/src/context_menu.rs                                         |   16 
crates/copilot/src/sign_in.rs                                                   |    6 
crates/copilot_button/src/copilot_button.rs                                     |    5 
crates/diagnostics/src/diagnostics.rs                                           |    4 
crates/diagnostics/src/items.rs                                                 |    4 
crates/editor/src/display_map/block_map.rs                                      |    1 
crates/editor/src/editor.rs                                                     |  132 
crates/editor/src/editor_tests.rs                                               |  178 
crates/editor/src/element.rs                                                    |   14 
crates/editor/src/scroll.rs                                                     |    2 
crates/editor/src/scroll/actions.rs                                             |   12 
crates/editor/src/scroll/scroll_amount.rs                                       |   32 
crates/feedback/src/deploy_feedback_button.rs                                   |    3 
crates/feedback/src/submit_feedback_button.rs                                   |    2 
crates/file_finder/src/file_finder.rs                                           |    2 
crates/fs/Cargo.toml                                                            |    3 
crates/fs/src/fs.rs                                                             |   95 
crates/gpui/src/app.rs                                                          |   23 
crates/gpui/src/color.rs                                                        |    5 
crates/gpui/src/elements.rs                                                     |   92 
crates/gpui/src/elements/container.rs                                           |   10 
crates/gpui/src/elements/image.rs                                               |    3 
crates/gpui/src/elements/label.rs                                               |    4 
crates/gpui/src/elements/svg.rs                                                 |   37 
crates/gpui/src/elements/tooltip.rs                                             |    5 
crates/gpui/src/executor.rs                                                     |    5 
crates/gpui/src/font_cache.rs                                                   |    3 
crates/gpui/src/fonts.rs                                                        |   33 
crates/gpui/src/gpui.rs                                                         |    2 
crates/gpui/src/platform.rs                                                     |    3 
crates/gpui/src/platform/mac/platform.rs                                        |    1 
crates/gpui/src/scene.rs                                                        |    3 
crates/gpui_macros/src/gpui_macros.rs                                           |   69 
crates/language/src/syntax_map.rs                                               |  239 
crates/language/src/syntax_map/syntax_map_tests.rs                              |   33 
crates/language_selector/src/active_buffer_language.rs                          |    2 
crates/language_selector/src/language_selector.rs                               |    2 
crates/language_tools/src/lsp_log.rs                                            |    8 
crates/language_tools/src/syntax_tree_view.rs                                   |    5 
crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift  |   88 
crates/live_kit_client/examples/test_app.rs                                     |   36 
crates/live_kit_client/src/prod.rs                                              |  211 
crates/live_kit_client/src/test.rs                                              |   42 
crates/lsp/src/lsp.rs                                                           |   27 
crates/outline/src/outline.rs                                                   |    2 
crates/picker/src/picker.rs                                                     |    8 
crates/project/src/project.rs                                                   |  127 
crates/project/src/project_tests.rs                                             |  124 
crates/project/src/worktree.rs                                                  |  666 
crates/project/src/worktree_tests.rs                                            |  620 
crates/project_panel/src/project_panel.rs                                       |  111 
crates/project_symbols/src/project_symbols.rs                                   |    7 
crates/recent_projects/src/recent_projects.rs                                   |    2 
crates/rpc/proto/zed.proto                                                      |   14 
crates/rpc/src/proto.rs                                                         |    4 
crates/rpc/src/rpc.rs                                                           |    2 
crates/search/src/buffer_search.rs                                              |   20 
crates/search/src/project_search.rs                                             |    8 
crates/terminal_view/src/terminal_panel.rs                                      |    1 
crates/theme/src/theme.rs                                                       |  318 
crates/theme/src/theme_settings.rs                                              |    3 
crates/theme/src/ui.rs                                                          |   43 
crates/theme_selector/src/theme_selector.rs                                     |    2 
crates/theme_testbench/Cargo.toml                                               |   19 
crates/theme_testbench/src/theme_testbench.rs                                   |  300 
crates/util/src/paths.rs                                                        |    1 
crates/vim/src/motion.rs                                                        |   27 
crates/vim/src/normal.rs                                                        |   90 
crates/vim/src/normal/case.rs                                                   |   64 
crates/vim/src/normal/change.rs                                                 |    9 
crates/vim/src/normal/delete.rs                                                 |    2 
crates/vim/src/normal/scroll.rs                                                 |  120 
crates/vim/src/normal/substitute.rs                                             |   73 
crates/vim/src/normal/yank.rs                                                   |    2 
crates/vim/src/test.rs                                                          |   41 
crates/vim/src/vim.rs                                                           |    7 
crates/vim/src/visual.rs                                                        |    2 
crates/welcome/src/base_keymap_picker.rs                                        |    2 
crates/workspace/src/dock.rs                                                    |    7 
crates/workspace/src/notifications.rs                                           |    4 
crates/workspace/src/pane.rs                                                    |   47 
crates/workspace/src/toolbar.rs                                                 |   76 
crates/workspace/src/workspace.rs                                               |   31 
crates/xtask/Cargo.toml                                                         |   13 
crates/xtask/src/cli.rs                                                         |   23 
crates/xtask/src/main.rs                                                        |   29 
crates/zed-actions/Cargo.toml                                                   |   10 
crates/zed-actions/src/lib.rs                                                   |   28 
crates/zed/Cargo.toml                                                           |    5 
crates/zed/src/languages/heex/injections.scm                                    |    2 
crates/zed/src/main.rs                                                          |   32 
crates/zed/src/zed.rs                                                           |   51 
docs/local-collaboration.md                                                     |   22 
docs/zed/syntax-highlighting.md                                                 |   79 
script/build-theme-types                                                        |   10 
script/start-local-collaboration                                                |    2 
styles/.gitignore                                                               |    1 
styles/.zed/settings.json                                                       |   20 
styles/package-lock.json                                                        | 1376 
styles/package.json                                                             |   13 
styles/src/buildTokens.ts                                                       |   86 
styles/src/buildTypes.ts                                                        |   64 
styles/src/component/icon_button.ts                                             |   86 
styles/src/component/text_button.ts                                             |   90 
styles/src/element/index.ts                                                     |    4 
styles/src/element/interactive.test.ts                                          |   56 
styles/src/element/interactive.ts                                               |   97 
styles/src/element/toggle.test.ts                                               |   52 
styles/src/element/toggle.ts                                                    |   47 
styles/src/styleTree/app.ts                                                     |    3 
styles/src/styleTree/assistant.ts                                               |  238 
styles/src/styleTree/commandPalette.ts                                          |   18 
styles/src/styleTree/components.ts                                              |   10 
styles/src/styleTree/contactList.ts                                             |  200 
styles/src/styleTree/contactNotification.ts                                     |   42 
styles/src/styleTree/contextMenu.ts                                             |   76 
styles/src/styleTree/copilot.ts                                                 |  185 
styles/src/styleTree/editor.ts                                                  |  114 
styles/src/styleTree/feedback.ts                                                |   51 
styles/src/styleTree/picker.ts                                                  |   87 
styles/src/styleTree/projectPanel.ts                                            |  227 
styles/src/styleTree/search.ts                                                  |  100 
styles/src/styleTree/simpleMessageNotification.ts                               |   59 
styles/src/styleTree/statusBar.ts                                               |  160 
styles/src/styleTree/tabBar.ts                                                  |   40 
styles/src/styleTree/titlebar.ts                                                |  266 
styles/src/styleTree/toggle.ts                                                  |   47 
styles/src/styleTree/toolbarDropdownMenu.ts                                     |   76 
styles/src/styleTree/updateNotification.ts                                      |   39 
styles/src/styleTree/welcome.ts                                                 |   43 
styles/src/styleTree/workspace.ts                                               |  258 
styles/src/theme/syntax.ts                                                      |    2 
styles/src/theme/tokens/colorScheme.ts                                          |   56 
styles/src/theme/tokens/layer.ts                                                |   33 
styles/src/theme/tokens/players.ts                                              |   18 
styles/src/theme/tokens/token.ts                                                |    9 
styles/src/themes/atelier/atelier-forest-light.ts                               |    2 
styles/src/themes/rose-pine/common.ts                                           |   75 
styles/src/themes/rose-pine/rose-pine-dawn.ts                                   |   48 
styles/src/themes/rose-pine/rose-pine-moon.ts                                   |   42 
styles/src/themes/rose-pine/rose-pine.ts                                        |   41 
styles/src/utils/slugify.ts                                                     |   11 
styles/tsconfig.json                                                            |   12 
styles/vitest.config.ts                                                         |    8 
516 files changed, 11,524 insertions(+), 2,957 deletions(-)

Detailed changes

.config/nextest.toml πŸ”—

@@ -0,0 +1,6 @@
+[test-groups]
+sequential-db-tests = { max-threads = 1 }
+
+[[profile.default.overrides]]
+filter = 'package(db)'
+test-group = 'sequential-db-tests'

.github/workflows/ci.yml πŸ”—

@@ -51,6 +51,7 @@ jobs:
           rustup set profile minimal
           rustup update stable
           rustup target add wasm32-wasi
+          cargo install cargo-nextest
 
       - name: Install Node
         uses: actions/setup-node@v2
@@ -70,7 +71,7 @@ jobs:
         run: cargo check --workspace
 
       - name: Run tests
-        run: cargo test --workspace --no-fail-fast
+        run: cargo nextest run --workspace --no-fail-fast
 
       - name: Build collab
         run: cargo build -p collab

.gitignore πŸ”—

@@ -4,6 +4,8 @@
 /plugins/bin
 /script/node_modules
 /styles/node_modules
+/styles/src/types/zed.ts
+/crates/theme/schemas/theme.json
 /crates/collab/static/styles.css
 /vendor/bin
 /assets/themes/*.json

Cargo.lock πŸ”—

@@ -109,6 +109,8 @@ dependencies = [
  "isahc",
  "language",
  "menu",
+ "project",
+ "regex",
  "schemars",
  "search",
  "serde",
@@ -190,6 +192,55 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anstream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal 0.4.7",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.71"
@@ -1102,8 +1153,8 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
 dependencies = [
  "atty",
  "bitflags",
- "clap_derive",
- "clap_lex",
+ "clap_derive 3.2.25",
+ "clap_lex 0.2.4",
  "indexmap",
  "once_cell",
  "strsim",
@@ -1111,6 +1162,30 @@ dependencies = [
  "textwrap",
 ]
 
+[[package]]
+name = "clap"
+version = "4.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc"
+dependencies = [
+ "clap_builder",
+ "clap_derive 4.3.2",
+ "once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "bitflags",
+ "clap_lex 0.5.0",
+ "strsim",
+]
+
 [[package]]
 name = "clap_derive"
 version = "3.2.25"
@@ -1124,6 +1199,18 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "clap_derive"
+version = "4.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
 [[package]]
 name = "clap_lex"
 version = "0.2.4"
@@ -1133,12 +1220,18 @@ dependencies = [
  "os_str_bytes",
 ]
 
+[[package]]
+name = "clap_lex"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+
 [[package]]
 name = "cli"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "clap",
+ "clap 3.2.25",
  "core-foundation",
  "core-services",
  "dirs 3.0.2",
@@ -1240,7 +1333,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.14.2"
+version = "0.15.0"
 dependencies = [
  "anyhow",
  "async-tungstenite",
@@ -1248,7 +1341,7 @@ dependencies = [
  "axum-extra",
  "base64 0.13.1",
  "call",
- "clap",
+ "clap 3.2.25",
  "client",
  "collections",
  "ctor",
@@ -1326,8 +1419,10 @@ dependencies = [
  "serde_derive",
  "settings",
  "theme",
+ "theme_selector",
  "util",
  "workspace",
+ "zed-actions",
 ]
 
 [[package]]
@@ -1343,6 +1438,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
 [[package]]
 name = "command_palette"
 version = "0.1.0"
@@ -6917,18 +7018,6 @@ dependencies = [
  "workspace",
 ]
 
-[[package]]
-name = "theme_testbench"
-version = "0.1.0"
-dependencies = [
- "gpui",
- "project",
- "settings",
- "smallvec",
- "theme",
- "workspace",
-]
-
 [[package]]
 name = "thiserror"
 version = "1.0.40"
@@ -8781,6 +8870,17 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
 
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap 4.3.5",
+ "schemars",
+ "serde_json",
+ "theme",
+]
+
 [[package]]
 name = "yaml-rust"
 version = "0.4.5"
@@ -8810,7 +8910,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.92.0"
+version = "0.94.0"
 dependencies = [
  "activity_indicator",
  "ai",
@@ -8889,7 +8989,6 @@ dependencies = [
  "text",
  "theme",
  "theme_selector",
- "theme_testbench",
  "thiserror",
  "tiny_http",
  "toml",
@@ -8921,6 +9020,14 @@ dependencies = [
  "vim",
  "welcome",
  "workspace",
+ "zed-actions",
+]
+
+[[package]]
+name = "zed-actions"
+version = "0.1.0"
+dependencies = [
+ "gpui",
 ]
 
 [[package]]

Cargo.toml πŸ”—

@@ -61,12 +61,13 @@ members = [
     "crates/text",
     "crates/theme",
     "crates/theme_selector",
-    "crates/theme_testbench",
     "crates/util",
     "crates/vim",
     "crates/workspace",
     "crates/welcome",
+    "crates/xtask",
     "crates/zed",
+    "crates/zed-actions"
 ]
 default-members = ["crates/zed"]
 resolver = "2"
@@ -118,3 +119,4 @@ split-debuginfo = "unpacked"
 [profile.release]
 debug = true
 lto = "thin"
+codegen-units = 1

assets/icons/hamburger_15.svg πŸ”—

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5 8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761 13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z" fill="#CCCAC2"/>
+</svg>

assets/icons/radix/align-bottom.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9 3C9 2.44772 8.55229 2 8 2H7C6.44772 2 6 2.44772 6 3L6 14H1.5C1.22386 14 1 14.2239 1 14.5C1 14.7761 1.22386 15 1.5 15L6 15H9H13.5C13.7761 15 14 14.7761 14 14.5C14 14.2239 13.7761 14 13.5 14H9V3Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-center-horizontally.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.99988 6C1.44759 6 0.999877 6.44772 0.999877 7L0.999877 8C0.999877 8.55228 1.44759 9 1.99988 9L6.99988 9L6.99988 13.5C6.99988 13.7761 7.22374 14 7.49988 14C7.77602 14 7.99988 13.7761 7.99988 13.5L7.99988 9L12.9999 9C13.5522 9 13.9999 8.55228 13.9999 8L13.9999 7C13.9999 6.44772 13.5522 6 12.9999 6L7.99988 6L7.99988 1.5C7.99988 1.22386 7.77602 1 7.49988 1C7.22373 1 6.99988 1.22386 6.99988 1.5L6.99988 6L1.99988 6Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-center-vertically.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.99988 1C6.44759 1 5.99988 1.44772 5.99988 2V7H1.49988C1.22374 7 0.999878 7.22386 0.999878 7.5C0.999878 7.77614 1.22374 8 1.49988 8H5.99988V13C5.99988 13.5523 6.44759 14 6.99988 14H7.99988C8.55216 14 8.99988 13.5523 8.99988 13V8H13.4999C13.776 8 13.9999 7.77614 13.9999 7.5C13.9999 7.22386 13.776 7 13.4999 7H8.99988V2C8.99988 1.44772 8.55216 1 7.99988 1L6.99988 1Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-center.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6 7.05002V4H9V7.05002L6 7.05002ZM5 7.05002H1.49919C1.25067 7.05002 1.04919 7.25149 1.04919 7.50002C1.04919 7.74855 1.25067 7.95002 1.49919 7.95002H5V11.25C5 11.6642 5.33579 12 5.75 12H9.25C9.66421 12 10 11.6642 10 11.25V7.95002H13.4992C13.7477 7.95002 13.9492 7.74855 13.9492 7.50002C13.9492 7.2515 13.7477 7.05002 13.4992 7.05002H10V3.75C10 3.33579 9.66421 3 9.25 3H5.75C5.33579 3 5 3.33579 5 3.75V7.05002ZM9 7.95002V11H6V7.95002L9 7.95002Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-end.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6 11V4H9V11H6ZM5 3.75C5 3.33579 5.33579 3 5.75 3H9.25C9.66421 3 10 3.33579 10 3.75V11.25C10 11.6642 9.66421 12 9.25 12H5.75C5.33579 12 5 11.6642 5 11.25V3.75ZM1.49919 13.05C1.25067 13.05 1.04919 13.2515 1.04919 13.5C1.04919 13.7486 1.25067 13.95 1.49919 13.95L13.4992 13.95C13.7477 13.95 13.9492 13.7486 13.9492 13.5C13.9492 13.2515 13.7477 13.05 13.4992 13.05H1.49919Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-horizontal-centers.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.24988 2C2.55952 2 1.99988 2.55964 1.99988 3.25V11.75C1.99988 12.4404 2.55952 13 3.24988 13H5.74988C6.44023 13 6.99988 12.4404 6.99988 11.75V3.25C6.99988 2.55964 6.44023 2 5.74988 2H3.24988ZM2.99988 3.25C2.99988 3.11193 3.11181 3 3.24988 3H5.74988C5.88795 3 5.99988 3.11193 5.99988 3.25V11.75C5.99988 11.8881 5.88795 12 5.74988 12H3.24988C3.11181 12 2.99988 11.8881 2.99988 11.75V3.25ZM9.25 4C8.55964 4 8 4.55964 8 5.25V9.75C8 10.4404 8.55964 11 9.25 11H11.75C12.4404 11 13 10.4404 13 9.75V5.25C13 4.55964 12.4404 4 11.75 4H9.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.499995 0.999995C0.223855 0.999995 -5.58458e-07 1.22385 -5.46388e-07 1.49999L-2.18554e-08 13.4999C-9.78492e-09 13.776 0.223855 13.9999 0.499995 13.9999C0.776136 13.9999 0.999991 13.776 0.999991 13.4999L0.999991 8.99993L12 8.99993C12.5523 8.99993 13 8.55222 13 7.99993L13 6.99994C13 6.44766 12.5523 5.99995 12 5.99995L0.999991 5.99995L0.999991 1.49999C0.999991 1.22385 0.776135 0.999995 0.499995 0.999995Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14.4999 1C14.2237 1 13.9999 1.22386 13.9999 1.5L13.9999 6L2.99988 6C2.44759 6 1.99988 6.44772 1.99988 7L1.99988 8C1.99988 8.55228 2.44759 9 2.99988 9L13.9999 9L13.9999 13.5C13.9999 13.7761 14.2237 14 14.4999 14C14.776 14 14.9999 13.7761 14.9999 13.5L14.9999 9L14.9999 6L14.9999 1.5C14.9999 1.22386 14.776 1 14.4999 1Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-start.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.49956 1.05002C1.25103 1.05002 1.04956 1.25149 1.04956 1.50002C1.04956 1.74855 1.25103 1.95002 1.49956 1.95002L13.4996 1.95002C13.7481 1.95002 13.9496 1.74855 13.9496 1.50002C13.9496 1.25149 13.7481 1.05002 13.4996 1.05002H1.49956ZM6 11V3.99999H9V11H6ZM5 3.74999C5 3.33578 5.33579 2.99999 5.75 2.99999H9.25C9.66421 2.99999 10 3.33578 10 3.74999V11.25C10 11.6642 9.66421 12 9.25 12H5.75C5.33579 12 5 11.6642 5 11.25V3.74999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-stretch.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.04956 1.50002C1.04956 1.25149 1.25103 1.05002 1.49956 1.05002H13.4996C13.7481 1.05002 13.9496 1.25149 13.9496 1.50002C13.9496 1.74855 13.7481 1.95002 13.4996 1.95002L1.49956 1.95002C1.25103 1.95002 1.04956 1.74855 1.04956 1.50002ZM1.04966 13.5C1.04966 13.2515 1.25113 13.05 1.49966 13.05H13.4997C13.7482 13.05 13.9497 13.2515 13.9497 13.5C13.9497 13.7485 13.7482 13.95 13.4997 13.95L1.49966 13.95C1.25113 13.95 1.04966 13.7485 1.04966 13.5ZM6 11V3.99999H9V11H6ZM5 3.74999C5 3.33578 5.33579 2.99999 5.75 2.99999H9.25C9.66421 2.99999 10 3.33578 10 3.74999V11.25C10 11.6642 9.66421 12 9.25 12H5.75C5.33579 12 5 11.6642 5 11.25V3.74999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-top.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.5 0C1.22386 0 1 0.223858 1 0.5C1 0.776142 1.22386 1 1.5 1H6V12C6 12.5523 6.44772 13 7 13H8C8.55228 13 9 12.5523 9 12V1H13.5C13.7761 1 14 0.776142 14 0.5C14 0.223858 13.7761 0 13.5 0H9H6H1.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/align-vertical-centers.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 3.25C2 2.55964 2.55964 2 3.25 2L11.75 2C12.4404 2 13 2.55964 13 3.25L13 5.75C13 6.44036 12.4404 7 11.75 7L3.25 7C2.55964 7 2 6.44036 2 5.75L2 3.25ZM3.25 3C3.11193 3 3 3.11193 3 3.25L3 5.75C3 5.88807 3.11193 6 3.25 6L11.75 6C11.8881 6 12 5.88807 12 5.75L12 3.25C12 3.11193 11.8881 3 11.75 3L3.25 3ZM4 9.25C4 8.55964 4.55964 8 5.25 8L9.75 8C10.4404 8 11 8.55964 11 9.25L11 11.75C11 12.4404 10.4404 13 9.75 13L5.25 13C4.55964 13 4 12.4404 4 11.75L4 9.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/all-sides.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 0.75L9.75 3H5.25L7.5 0.75ZM7.5 14.25L9.75 12H5.25L7.5 14.25ZM3 5.25L0.75 7.5L3 9.75V5.25ZM14.25 7.5L12 5.25V9.75L14.25 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/angle.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.8914 2.1937C9.1158 2.35464 9.16725 2.66701 9.00631 2.89141L2.47388 12H13.5C13.7761 12 14 12.2239 14 12.5C14 12.7762 13.7761 13 13.5 13H1.5C1.31254 13 1.14082 12.8952 1.0552 12.7284C0.969578 12.5616 0.984438 12.361 1.09369 12.2086L8.19369 2.30862C8.35462 2.08422 8.667 2.03277 8.8914 2.1937ZM11.1 6.50001C11.1 6.22387 11.3238 6.00001 11.6 6.00001C11.8761 6.00001 12.1 6.22387 12.1 6.50001C12.1 6.77615 11.8761 7.00001 11.6 7.00001C11.3238 7.00001 11.1 6.77615 11.1 6.50001ZM10.4 4.00001C10.1239 4.00001 9.90003 4.22387 9.90003 4.50001C9.90003 4.77615 10.1239 5.00001 10.4 5.00001C10.6762 5.00001 10.9 4.77615 10.9 4.50001C10.9 4.22387 10.6762 4.00001 10.4 4.00001ZM12.1 8.50001C12.1 8.22387 12.3238 8.00001 12.6 8.00001C12.8761 8.00001 13.1 8.22387 13.1 8.50001C13.1 8.77615 12.8761 9.00001 12.6 9.00001C12.3238 9.00001 12.1 8.77615 12.1 8.50001ZM13.4 10C13.1239 10 12.9 10.2239 12.9 10.5C12.9 10.7761 13.1239 11 13.4 11C13.6762 11 13.9 10.7761 13.9 10.5C13.9 10.2239 13.6762 10 13.4 10Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/archive.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.30902 1C2.93025 1 2.58398 1.214 2.41459 1.55279L1.05279 4.27639C1.01807 4.34582 1 4.42238 1 4.5V13C1 13.5523 1.44772 14 2 14H13C13.5523 14 14 13.5523 14 13V4.5C14 4.42238 13.9819 4.34582 13.9472 4.27639L12.5854 1.55281C12.416 1.21403 12.0698 1.00003 11.691 1.00003L7.5 1.00001L3.30902 1ZM3.30902 2L7 2.00001V4H2.30902L3.30902 2ZM8 4V2.00002L11.691 2.00003L12.691 4H8ZM7.5 5H13V13H2V5H7.5ZM5.5 7C5.22386 7 5 7.22386 5 7.5C5 7.77614 5.22386 8 5.5 8H9.5C9.77614 8 10 7.77614 10 7.5C10 7.22386 9.77614 7 9.5 7H5.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-bottom-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.3536 3.64644C11.5488 3.8417 11.5488 4.15828 11.3536 4.35354L4.70711 11L9 11C9.27614 11 9.5 11.2239 9.5 11.5C9.5 11.7761 9.27614 12 9 12L3.5 12C3.36739 12 3.24021 11.9473 3.14645 11.8536C3.05268 11.7598 3 11.6326 3 11.5L3 5.99999C3 5.72385 3.22386 5.49999 3.5 5.49999C3.77614 5.49999 4 5.72385 4 5.99999V10.2929L10.6464 3.64643C10.8417 3.45117 11.1583 3.45117 11.3536 3.64644Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-bottom-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.64645 3.64644C3.45118 3.8417 3.45118 4.15828 3.64645 4.35354L10.2929 11L6 11C5.72386 11 5.5 11.2239 5.5 11.5C5.5 11.7761 5.72386 12 6 12L11.5 12C11.6326 12 11.7598 11.9473 11.8536 11.8536C11.9473 11.7598 12 11.6326 12 11.5L12 5.99999C12 5.72385 11.7761 5.49999 11.5 5.49999C11.2239 5.49999 11 5.72385 11 5.99999V10.2929L4.35355 3.64643C4.15829 3.45117 3.84171 3.45117 3.64645 3.64644Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-down.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 2C7.77614 2 8 2.22386 8 2.5L8 11.2929L11.1464 8.14645C11.3417 7.95118 11.6583 7.95118 11.8536 8.14645C12.0488 8.34171 12.0488 8.65829 11.8536 8.85355L7.85355 12.8536C7.75979 12.9473 7.63261 13 7.5 13C7.36739 13 7.24021 12.9473 7.14645 12.8536L3.14645 8.85355C2.95118 8.65829 2.95118 8.34171 3.14645 8.14645C3.34171 7.95118 3.65829 7.95118 3.85355 8.14645L7 11.2929L7 2.5C7 2.22386 7.22386 2 7.5 2Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.85355 3.14645C7.04882 3.34171 7.04882 3.65829 6.85355 3.85355L3.70711 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H3.70711L6.85355 11.1464C7.04882 11.3417 7.04882 11.6583 6.85355 11.8536C6.65829 12.0488 6.34171 12.0488 6.14645 11.8536L2.14645 7.85355C1.95118 7.65829 1.95118 7.34171 2.14645 7.14645L6.14645 3.14645C6.34171 2.95118 6.65829 2.95118 6.85355 3.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.14645 3.14645C8.34171 2.95118 8.65829 2.95118 8.85355 3.14645L12.8536 7.14645C13.0488 7.34171 13.0488 7.65829 12.8536 7.85355L8.85355 11.8536C8.65829 12.0488 8.34171 12.0488 8.14645 11.8536C7.95118 11.6583 7.95118 11.3417 8.14645 11.1464L11.2929 8H2.5C2.22386 8 2 7.77614 2 7.5C2 7.22386 2.22386 7 2.5 7H11.2929L8.14645 3.85355C7.95118 3.65829 7.95118 3.34171 8.14645 3.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-top-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.3536 11.3536C11.5488 11.1583 11.5488 10.8417 11.3536 10.6465L4.70711 4L9 4C9.27614 4 9.5 3.77614 9.5 3.5C9.5 3.22386 9.27614 3 9 3L3.5 3C3.36739 3 3.24021 3.05268 3.14645 3.14645C3.05268 3.24022 3 3.36739 3 3.5L3 9.00001C3 9.27615 3.22386 9.50001 3.5 9.50001C3.77614 9.50001 4 9.27615 4 9.00001V4.70711L10.6464 11.3536C10.8417 11.5488 11.1583 11.5488 11.3536 11.3536Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-top-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.64645 11.3536C3.45118 11.1583 3.45118 10.8417 3.64645 10.6465L10.2929 4L6 4C5.72386 4 5.5 3.77614 5.5 3.5C5.5 3.22386 5.72386 3 6 3L11.5 3C11.6326 3 11.7598 3.05268 11.8536 3.14645C11.9473 3.24022 12 3.36739 12 3.5L12 9.00001C12 9.27615 11.7761 9.50001 11.5 9.50001C11.2239 9.50001 11 9.27615 11 9.00001V4.70711L4.35355 11.3536C4.15829 11.5488 3.84171 11.5488 3.64645 11.3536Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/arrow-up.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.14645 2.14645C7.34171 1.95118 7.65829 1.95118 7.85355 2.14645L11.8536 6.14645C12.0488 6.34171 12.0488 6.65829 11.8536 6.85355C11.6583 7.04882 11.3417 7.04882 11.1464 6.85355L8 3.70711L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 3.70711L3.85355 6.85355C3.65829 7.04882 3.34171 7.04882 3.14645 6.85355C2.95118 6.65829 2.95118 6.34171 3.14645 6.14645L7.14645 2.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/aspect-ratio.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.5 2H12.5C12.7761 2 13 2.22386 13 2.5V12.5C13 12.7761 12.7761 13 12.5 13H2.5C2.22386 13 2 12.7761 2 12.5V2.5C2 2.22386 2.22386 2 2.5 2ZM1 2.5C1 1.67157 1.67157 1 2.5 1H12.5C13.3284 1 14 1.67157 14 2.5V12.5C14 13.3284 13.3284 14 12.5 14H2.5C1.67157 14 1 13.3284 1 12.5V2.5ZM7.5 4C7.77614 4 8 3.77614 8 3.5C8 3.22386 7.77614 3 7.5 3C7.22386 3 7 3.22386 7 3.5C7 3.77614 7.22386 4 7.5 4ZM8 5.5C8 5.77614 7.77614 6 7.5 6C7.22386 6 7 5.77614 7 5.5C7 5.22386 7.22386 5 7.5 5C7.77614 5 8 5.22386 8 5.5ZM7.5 8C7.77614 8 8 7.77614 8 7.5C8 7.22386 7.77614 7 7.5 7C7.22386 7 7 7.22386 7 7.5C7 7.77614 7.22386 8 7.5 8ZM10 7.5C10 7.77614 9.77614 8 9.5 8C9.22386 8 9 7.77614 9 7.5C9 7.22386 9.22386 7 9.5 7C9.77614 7 10 7.22386 10 7.5ZM11.5 8C11.7761 8 12 7.77614 12 7.5C12 7.22386 11.7761 7 11.5 7C11.2239 7 11 7.22386 11 7.5C11 7.77614 11.2239 8 11.5 8Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/avatar.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/backpack.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5 1C5 0.447715 5.44772 0 6 0H9C9.55228 0 10 0.447715 10 1V2H14C14.5523 2 15 2.44772 15 3V6C15 6.8888 14.6131 7.68734 14 8.23608V11.5C14 12.3284 13.3284 13 12.5 13H2.5C1.67157 13 1 12.3284 1 11.5V8.2359C0.38697 7.68721 0 6.88883 0 6V3C0 2.44772 0.447716 2 1 2H5V1ZM9 1V2H6V1H9ZM1 3H5H5.5H9.5H10H14V6C14 6.654 13.6866 7.23467 13.1997 7.6004C12.8655 7.85144 12.4508 8 12 8H8V7.5C8 7.22386 7.77614 7 7.5 7C7.22386 7 7 7.22386 7 7.5V8H3C2.5493 8 2.1346 7.85133 1.80029 7.60022C1.31335 7.23446 1 6.65396 1 6V3ZM7 9H3C2.64961 9 2.31292 8.93972 2 8.82905V11.5C2 11.7761 2.22386 12 2.5 12H12.5C12.7761 12 13 11.7761 13 11.5V8.82915C12.6871 8.93978 12.3504 9 12 9H8V9.5C8 9.77614 7.77614 10 7.5 10C7.22386 10 7 9.77614 7 9.5V9Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/badge.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.5 6H11.5C12.3284 6 13 6.67157 13 7.5C13 8.32843 12.3284 9 11.5 9H3.5C2.67157 9 2 8.32843 2 7.5C2 6.67157 2.67157 6 3.5 6ZM1 7.5C1 6.11929 2.11929 5 3.5 5H11.5C12.8807 5 14 6.11929 14 7.5C14 8.88071 12.8807 10 11.5 10H3.5C2.11929 10 1 8.88071 1 7.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/bar-chart.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.5 1C11.7761 1 12 1.22386 12 1.5V13.5C12 13.7761 11.7761 14 11.5 14C11.2239 14 11 13.7761 11 13.5V1.5C11 1.22386 11.2239 1 11.5 1ZM9.5 3C9.77614 3 10 3.22386 10 3.5V13.5C10 13.7761 9.77614 14 9.5 14C9.22386 14 9 13.7761 9 13.5V3.5C9 3.22386 9.22386 3 9.5 3ZM13.5 3C13.7761 3 14 3.22386 14 3.5V13.5C14 13.7761 13.7761 14 13.5 14C13.2239 14 13 13.7761 13 13.5V3.5C13 3.22386 13.2239 3 13.5 3ZM5.5 4C5.77614 4 6 4.22386 6 4.5V13.5C6 13.7761 5.77614 14 5.5 14C5.22386 14 5 13.7761 5 13.5V4.5C5 4.22386 5.22386 4 5.5 4ZM1.5 5C1.77614 5 2 5.22386 2 5.5V13.5C2 13.7761 1.77614 14 1.5 14C1.22386 14 1 13.7761 1 13.5V5.5C1 5.22386 1.22386 5 1.5 5ZM7.5 5C7.77614 5 8 5.22386 8 5.5V13.5C8 13.7761 7.77614 14 7.5 14C7.22386 14 7 13.7761 7 13.5V5.5C7 5.22386 7.22386 5 7.5 5ZM3.5 7C3.77614 7 4 7.22386 4 7.5V13.5C4 13.7761 3.77614 14 3.5 14C3.22386 14 3 13.7761 3 13.5V7.5C3 7.22386 3.22386 7 3.5 7Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/bell.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/blending-mode.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 9C3 6.5 4.5 4.25 7.5 1.5C10.5 4.25 12 6.5 12 9C12 11.4853 9.98528 13.5 7.5 13.5C5.01472 13.5 3 11.4853 3 9ZM10.9524 8.30307C9.67347 7.82121 8.2879 8.46208 6.98956 9.06259C5.9327 9.55142 4.93365 10.0135 4.09695 9.82153C4.03357 9.55804 4 9.28294 4 9C4 7.11203 5.02686 5.27195 7.5 2.87357C9.66837 4.97639 10.725 6.65004 10.9524 8.30307Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/bookmark-filled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.5 2C3.22386 2 3 2.22386 3 2.5V13.5C3 13.6818 3.09864 13.8492 3.25762 13.9373C3.41659 14.0254 3.61087 14.0203 3.765 13.924L7.5 11.5896L11.235 13.924C11.3891 14.0203 11.5834 14.0254 11.7424 13.9373C11.9014 13.8492 12 13.6818 12 13.5V2.5C12 2.22386 11.7761 2 11.5 2H3.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/bookmark.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 2.5C3 2.22386 3.22386 2 3.5 2H11.5C11.7761 2 12 2.22386 12 2.5V13.5C12 13.6818 11.9014 13.8492 11.7424 13.9373C11.5834 14.0254 11.3891 14.0203 11.235 13.924L7.5 11.5896L3.765 13.924C3.61087 14.0203 3.41659 14.0254 3.25762 13.9373C3.09864 13.8492 3 13.6818 3 13.5V2.5ZM4 3V12.5979L6.97 10.7416C7.29427 10.539 7.70573 10.539 8.03 10.7416L11 12.5979V3H4Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/border-all.svg πŸ”—

@@ -0,0 +1,17 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.25 1C0.25 0.585786 0.585786 0.25 1 0.25H14C14.4142 0.25 14.75 0.585786 14.75 1V14C14.75 14.4142 14.4142 14.75 14 14.75H1C0.585786 14.75 0.25 14.4142 0.25 14V1ZM1.75 1.75V13.25H13.25V1.75H1.75Z"
+    fill="currentColor"
+  />
+  <rect x="7" y="5" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="3" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="9" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="11" width="1" height="1" rx=".5" fill="currentColor" />
+</svg>

assets/icons/radix/border-bottom.svg πŸ”—

@@ -0,0 +1,29 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M1 13.25L14 13.25V14.75L1 14.75V13.25Z" fill="currentColor" />
+  <rect x="7" y="5" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="5" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="3" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="3" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="9" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="9" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="11" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="11" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="5" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="3" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="7" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="1" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="9" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="11" width="1" height="1" rx=".5" fill="currentColor" />
+</svg>

assets/icons/radix/border-dashed.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0 7.5C0 7.22386 0.223858 7 0.5 7H3C3.27614 7 3.5 7.22386 3.5 7.5C3.5 7.77614 3.27614 8 3 8H0.5C0.223858 8 0 7.77614 0 7.5ZM5.75 7.5C5.75 7.22386 5.97386 7 6.25 7H8.75C9.02614 7 9.25 7.22386 9.25 7.5C9.25 7.77614 9.02614 8 8.75 8H6.25C5.97386 8 5.75 7.77614 5.75 7.5ZM12 7C11.7239 7 11.5 7.22386 11.5 7.5C11.5 7.77614 11.7239 8 12 8H14.5C14.7761 8 15 7.77614 15 7.5C15 7.22386 14.7761 7 14.5 7H12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/border-dotted.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.5 6.625C1.01675 6.625 0.625 7.01675 0.625 7.5C0.625 7.98325 1.01675 8.375 1.5 8.375C1.98325 8.375 2.375 7.98325 2.375 7.5C2.375 7.01675 1.98325 6.625 1.5 6.625ZM5.5 6.625C5.01675 6.625 4.625 7.01675 4.625 7.5C4.625 7.98325 5.01675 8.375 5.5 8.375C5.98325 8.375 6.375 7.98325 6.375 7.5C6.375 7.01675 5.98325 6.625 5.5 6.625ZM9.5 6.625C9.01675 6.625 8.625 7.01675 8.625 7.5C8.625 7.98325 9.01675 8.375 9.5 8.375C9.98325 8.375 10.375 7.98325 10.375 7.5C10.375 7.01675 9.98325 6.625 9.5 6.625ZM12.625 7.5C12.625 7.01675 13.0168 6.625 13.5 6.625C13.9832 6.625 14.375 7.01675 14.375 7.5C14.375 7.98325 13.9832 8.375 13.5 8.375C13.0168 8.375 12.625 7.98325 12.625 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/border-left.svg πŸ”—

@@ -0,0 +1,29 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M1.75 1L1.75 14L0.249999 14L0.25 1L1.75 1Z" fill="currentColor" />
+  <rect x="10" y="7" width="1" height="1" rx=".5" transform="rotate(90 10 7)" fill="currentColor" />
+  <rect x="10" y="13" width="1" height="1" rx=".5" transform="rotate(90 10 13)" fill="currentColor" />
+  <rect x="12" y="7" width="1" height="1" rx=".5" transform="rotate(90 12 7)" fill="currentColor" />
+  <rect x="12" y="13" width="1" height="1" rx=".5" transform="rotate(90 12 13)" fill="currentColor" />
+  <rect x="8" y="7" width="1" height="1" rx=".5" transform="rotate(90 8 7)" fill="currentColor" />
+  <rect x="14" y="7" width="1" height="1" rx=".5" transform="rotate(90 14 7)" fill="currentColor" />
+  <rect x="8" y="13" width="1" height="1" rx=".5" transform="rotate(90 8 13)" fill="currentColor" />
+  <rect x="14" y="13" width="1" height="1" rx=".5" transform="rotate(90 14 13)" fill="currentColor" />
+  <rect x="8" y="5" width="1" height="1" rx=".5" transform="rotate(90 8 5)" fill="currentColor" />
+  <rect x="14" y="5" width="1" height="1" rx=".5" transform="rotate(90 14 5)" fill="currentColor" />
+  <rect x="8" y="3" width="1" height="1" rx=".5" transform="rotate(90 8 3)" fill="currentColor" />
+  <rect x="14" y="3" width="1" height="1" rx=".5" transform="rotate(90 14 3)" fill="currentColor" />
+  <rect x="8" y="9" width="1" height="1" rx=".5" transform="rotate(90 8 9)" fill="currentColor" />
+  <rect x="14" y="9" width="1" height="1" rx=".5" transform="rotate(90 14 9)" fill="currentColor" />
+  <rect x="8" y="11" width="1" height="1" rx=".5" transform="rotate(90 8 11)" fill="currentColor" />
+  <rect x="14" y="11" width="1" height="1" rx=".5" transform="rotate(90 14 11)" fill="currentColor" />
+  <rect x="6" y="7" width="1" height="1" rx=".5" transform="rotate(90 6 7)" fill="currentColor" />
+  <rect x="6" y="13" width="1" height="1" rx=".5" transform="rotate(90 6 13)" fill="currentColor" />
+  <rect x="4" y="7" width="1" height="1" rx=".5" transform="rotate(90 4 7)" fill="currentColor" />
+  <rect x="4" y="13" width="1" height="1" rx=".5" transform="rotate(90 4 13)" fill="currentColor" />
+  <rect x="10" y="1" width="1" height="1" rx=".5" transform="rotate(90 10 1)" fill="currentColor" />
+  <rect x="12" y="1" width="1" height="1" rx=".5" transform="rotate(90 12 1)" fill="currentColor" />
+  <rect x="8" y="1" width="1" height="1" rx=".5" transform="rotate(90 8 1)" fill="currentColor" />
+  <rect x="14" y="1" width="1" height="1" rx=".5" transform="rotate(90 14 1)" fill="currentColor" />
+  <rect x="6" y="1" width="1" height="1" rx=".5" transform="rotate(90 6 1)" fill="currentColor" />
+  <rect x="4" y="1" width="1" height="1" rx=".5" transform="rotate(90 4 1)" fill="currentColor" />
+</svg>

assets/icons/radix/border-none.svg πŸ”—

@@ -0,0 +1,35 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect x="7" y="5.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="5.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="3.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="3.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="9.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="9.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="11.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="11.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="5.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="3.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="9.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="11.025" width="1" height="1" rx=".5" fill="currentColor" />
+</svg>

assets/icons/radix/border-right.svg πŸ”—

@@ -0,0 +1,29 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M13.25 1L13.25 14L14.75 14L14.75 1L13.25 1Z" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 5 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 5 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 3 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 3 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 5)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 5)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 3)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 3)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 9)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 9)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 11)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 11)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 9 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 9 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 11 7)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 11 13)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 5 1)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 3 1)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 7 1)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 1 1)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 9 1)" fill="currentColor" />
+  <rect width="1" height="1" rx=".5" transform="matrix(0 1 1 0 11 1)" fill="currentColor" />
+</svg>

assets/icons/radix/border-solid.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.25 7.5C1.25 7.22386 1.47386 7 1.75 7H13.25C13.5261 7 13.75 7.22386 13.75 7.5C13.75 7.77614 13.5261 8 13.25 8H1.75C1.47386 8 1.25 7.77614 1.25 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/border-split.svg πŸ”—

@@ -0,0 +1,21 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect x="7" y="5.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="3.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="13.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="1.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="13" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="5" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="3" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="9" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="11" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="9.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="7" y="11.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <rect x="1" y="7.025" width="1" height="1" rx=".5" fill="currentColor" />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 1.49994C1 1.2238 1.22386 0.999939 1.5 0.999939H6V1.99994H2V5.99994H1V1.49994ZM13 1.99994H9V0.999939H13.5C13.7761 0.999939 14 1.2238 14 1.49994V5.99994H13V1.99994ZM1 13.4999V8.99994H2V12.9999H6V13.9999H1.5C1.22386 13.9999 1 13.7761 1 13.4999ZM13 12.9999V8.99994H14V13.4999C14 13.7761 13.7761 13.9999 13.5 13.9999H9.5V12.9999H13Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/border-top.svg πŸ”—

@@ -0,0 +1,29 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M14 1.75L1 1.75L1 0.249999L14 0.25L14 1.75Z" fill="currentColor" />
+  <rect x="8" y="10" width="1" height="1" rx=".5" transform="rotate(-180 8 10)" fill="currentColor" />
+  <rect x="2" y="10" width="1" height="1" rx=".5" transform="rotate(-180 2 10)" fill="currentColor" />
+  <rect x="8" y="12" width="1" height="1" rx=".5" transform="rotate(-180 8 12)" fill="currentColor" />
+  <rect x="2" y="12" width="1" height="1" rx=".5" transform="rotate(-180 2 12)" fill="currentColor" />
+  <rect x="8" y="8" width="1" height="1" rx=".5" transform="rotate(-180 8 8)" fill="currentColor" />
+  <rect x="8" y="14" width="1" height="1" rx=".5" transform="rotate(-180 8 14)" fill="currentColor" />
+  <rect x="2" y="8" width="1" height="1" rx=".5" transform="rotate(-180 2 8)" fill="currentColor" />
+  <rect x="2" y="14" width="1" height="1" rx=".5" transform="rotate(-180 2 14)" fill="currentColor" />
+  <rect x="10" y="8" width="1" height="1" rx=".5" transform="rotate(-180 10 8)" fill="currentColor" />
+  <rect x="10" y="14" width="1" height="1" rx=".5" transform="rotate(-180 10 14)" fill="currentColor" />
+  <rect x="12" y="8" width="1" height="1" rx=".5" transform="rotate(-180 12 8)" fill="currentColor" />
+  <rect x="12" y="14" width="1" height="1" rx=".5" transform="rotate(-180 12 14)" fill="currentColor" />
+  <rect x="6" y="8" width="1" height="1" rx=".5" transform="rotate(-180 6 8)" fill="currentColor" />
+  <rect x="6" y="14" width="1" height="1" rx=".5" transform="rotate(-180 6 14)" fill="currentColor" />
+  <rect x="4" y="8" width="1" height="1" rx=".5" transform="rotate(-180 4 8)" fill="currentColor" />
+  <rect x="4" y="14" width="1" height="1" rx=".5" transform="rotate(-180 4 14)" fill="currentColor" />
+  <rect x="8" y="6" width="1" height="1" rx=".5" transform="rotate(-180 8 6)" fill="currentColor" />
+  <rect x="2" y="6" width="1" height="1" rx=".5" transform="rotate(-180 2 6)" fill="currentColor" />
+  <rect x="8" y="4" width="1" height="1" rx=".5" transform="rotate(-180 8 4)" fill="currentColor" />
+  <rect x="2" y="4" width="1" height="1" rx=".5" transform="rotate(-180 2 4)" fill="currentColor" />
+  <rect x="14" y="10" width="1" height="1" rx=".5" transform="rotate(-180 14 10)" fill="currentColor" />
+  <rect x="14" y="12" width="1" height="1" rx=".5" transform="rotate(-180 14 12)" fill="currentColor" />
+  <rect x="14" y="8" width="1" height="1" rx=".5" transform="rotate(-180 14 8)" fill="currentColor" />
+  <rect x="14" y="14" width="1" height="1" rx=".5" transform="rotate(-180 14 14)" fill="currentColor" />
+  <rect x="14" y="6" width="1" height="1" rx=".5" transform="rotate(-180 14 6)" fill="currentColor" />
+  <rect x="14" y="4" width="1" height="1" rx=".5" transform="rotate(-180 14 4)" fill="currentColor" />
+</svg>

assets/icons/radix/border-width.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 3H14V4H1V3ZM1 6H14V8H1V6ZM14 10.25H1V12.75H14V10.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/box-model.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.99998 0.999976C1.44769 0.999976 0.999976 1.44769 0.999976 1.99998V13C0.999976 13.5523 1.44769 14 1.99998 14H13C13.5523 14 14 13.5523 14 13V1.99998C14 1.44769 13.5523 0.999976 13 0.999976H1.99998ZM1.99998 1.99998L13 1.99998V13H1.99998V1.99998ZM4.49996 3.99996C4.22382 3.99996 3.99996 4.22382 3.99996 4.49996V10.5C3.99996 10.7761 4.22382 11 4.49996 11H10.5C10.7761 11 11 10.7761 11 10.5V4.49996C11 4.22382 10.7761 3.99996 10.5 3.99996H4.49996ZM4.99996 9.99996V4.99996H9.99996V9.99996H4.99996Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/box.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.5 2H2.5C2.22386 2 2 2.22386 2 2.5V12.5C2 12.7761 2.22386 13 2.5 13H12.5C12.7761 13 13 12.7761 13 12.5V2.5C13 2.22386 12.7761 2 12.5 2ZM2.5 1C1.67157 1 1 1.67157 1 2.5V12.5C1 13.3284 1.67157 14 2.5 14H12.5C13.3284 14 14 13.3284 14 12.5V2.5C14 1.67157 13.3284 1 12.5 1H2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/button.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 5H13C13.5523 5 14 5.44772 14 6V9C14 9.55228 13.5523 10 13 10H2C1.44772 10 1 9.55228 1 9V6C1 5.44772 1.44772 5 2 5ZM0 6C0 4.89543 0.895431 4 2 4H13C14.1046 4 15 4.89543 15 6V9C15 10.1046 14.1046 11 13 11H2C0.89543 11 0 10.1046 0 9V6ZM4.5 6.75C4.08579 6.75 3.75 7.08579 3.75 7.5C3.75 7.91421 4.08579 8.25 4.5 8.25C4.91421 8.25 5.25 7.91421 5.25 7.5C5.25 7.08579 4.91421 6.75 4.5 6.75ZM6.75 7.5C6.75 7.08579 7.08579 6.75 7.5 6.75C7.91421 6.75 8.25 7.08579 8.25 7.5C8.25 7.91421 7.91421 8.25 7.5 8.25C7.08579 8.25 6.75 7.91421 6.75 7.5ZM10.5 6.75C10.0858 6.75 9.75 7.08579 9.75 7.5C9.75 7.91421 10.0858 8.25 10.5 8.25C10.9142 8.25 11.25 7.91421 11.25 7.5C11.25 7.08579 10.9142 6.75 10.5 6.75Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/calendar.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/camera.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 3C1.44772 3 1 3.44772 1 4V11C1 11.5523 1.44772 12 2 12H13C13.5523 12 14 11.5523 14 11V4C14 3.44772 13.5523 3 13 3H2ZM0 4C0 2.89543 0.895431 2 2 2H13C14.1046 2 15 2.89543 15 4V11C15 12.1046 14.1046 13 13 13H2C0.895431 13 0 12.1046 0 11V4ZM2 4.25C2 4.11193 2.11193 4 2.25 4H4.75C4.88807 4 5 4.11193 5 4.25V5.75454C5 5.89261 4.88807 6.00454 4.75 6.00454H2.25C2.11193 6.00454 2 5.89261 2 5.75454V4.25ZM12.101 7.58421C12.101 9.02073 10.9365 10.1853 9.49998 10.1853C8.06346 10.1853 6.89893 9.02073 6.89893 7.58421C6.89893 6.14769 8.06346 4.98315 9.49998 4.98315C10.9365 4.98315 12.101 6.14769 12.101 7.58421ZM13.101 7.58421C13.101 9.57302 11.4888 11.1853 9.49998 11.1853C7.51117 11.1853 5.89893 9.57302 5.89893 7.58421C5.89893 5.5954 7.51117 3.98315 9.49998 3.98315C11.4888 3.98315 13.101 5.5954 13.101 7.58421Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/card-stack-minus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.5 3C2.22386 3 2 3.22386 2 3.5V9.5C2 9.77614 2.22386 10 2.5 10H12.5C12.7761 10 13 9.77614 13 9.5V3.5C13 3.22386 12.7761 3 12.5 3H2.5ZM1 9.5C1 10.1531 1.4174 10.7087 2 10.9146V11.5C2 12.3284 2.67157 13 3.5 13H11.5C12.3284 13 13 12.3284 13 11.5V10.9146C13.5826 10.7087 14 10.1531 14 9.5V3.5C14 2.67157 13.3284 2 12.5 2H2.5C1.67157 2 1 2.67157 1 3.5V9.5ZM12 11.5V11H3V11.5C3 11.7761 3.22386 12 3.5 12H11.5C11.7761 12 12 11.7761 12 11.5ZM5.5 6C5.22386 6 5 6.22386 5 6.5C5 6.77614 5.22386 7 5.5 7H9.5C9.77614 7 10 6.77614 10 6.5C10 6.22386 9.77614 6 9.5 6H5.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/card-stack-plus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 3.5C2 3.22386 2.22386 3 2.5 3H12.5C12.7761 3 13 3.22386 13 3.5V9.5C13 9.77614 12.7761 10 12.5 10H2.5C2.22386 10 2 9.77614 2 9.5V3.5ZM2 10.9146C1.4174 10.7087 1 10.1531 1 9.5V3.5C1 2.67157 1.67157 2 2.5 2H12.5C13.3284 2 14 2.67157 14 3.5V9.5C14 10.1531 13.5826 10.7087 13 10.9146V11.5C13 12.3284 12.3284 13 11.5 13H3.5C2.67157 13 2 12.3284 2 11.5V10.9146ZM12 11V11.5C12 11.7761 11.7761 12 11.5 12H3.5C3.22386 12 3 11.7761 3 11.5V11H12ZM5 6.5C5 6.22386 5.22386 6 5.5 6H7V4.5C7 4.22386 7.22386 4 7.5 4C7.77614 4 8 4.22386 8 4.5V6H9.5C9.77614 6 10 6.22386 10 6.5C10 6.77614 9.77614 7 9.5 7H8V8.5C8 8.77614 7.77614 9 7.5 9C7.22386 9 7 8.77614 7 8.5V7H5.5C5.22386 7 5 6.77614 5 6.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/card-stack.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 3.5C2 3.22386 2.22386 3 2.5 3H12.5C12.7761 3 13 3.22386 13 3.5V9.5C13 9.77614 12.7761 10 12.5 10H2.5C2.22386 10 2 9.77614 2 9.5V3.5ZM2 10.9146C1.4174 10.7087 1 10.1531 1 9.5V3.5C1 2.67157 1.67157 2 2.5 2H12.5C13.3284 2 14 2.67157 14 3.5V9.5C14 10.1531 13.5826 10.7087 13 10.9146V11.5C13 12.3284 12.3284 13 11.5 13H3.5C2.67157 13 2 12.3284 2 11.5V10.9146ZM12 11V11.5C12 11.7761 11.7761 12 11.5 12H3.5C3.22386 12 3 11.7761 3 11.5V11H12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/caret-down.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.18179 6.18181C4.35753 6.00608 4.64245 6.00608 4.81819 6.18181L7.49999 8.86362L10.1818 6.18181C10.3575 6.00608 10.6424 6.00608 10.8182 6.18181C10.9939 6.35755 10.9939 6.64247 10.8182 6.81821L7.81819 9.81821C7.73379 9.9026 7.61934 9.95001 7.49999 9.95001C7.38064 9.95001 7.26618 9.9026 7.18179 9.81821L4.18179 6.81821C4.00605 6.64247 4.00605 6.35755 4.18179 6.18181Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/caret-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.81809 4.18179C8.99383 4.35753 8.99383 4.64245 8.81809 4.81819L6.13629 7.49999L8.81809 10.1818C8.99383 10.3575 8.99383 10.6424 8.81809 10.8182C8.64236 10.9939 8.35743 10.9939 8.1817 10.8182L5.1817 7.81819C5.09731 7.73379 5.0499 7.61933 5.0499 7.49999C5.0499 7.38064 5.09731 7.26618 5.1817 7.18179L8.1817 4.18179C8.35743 4.00605 8.64236 4.00605 8.81809 4.18179Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/caret-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.18194 4.18185C6.35767 4.00611 6.6426 4.00611 6.81833 4.18185L9.81833 7.18185C9.90272 7.26624 9.95013 7.3807 9.95013 7.50005C9.95013 7.6194 9.90272 7.73386 9.81833 7.81825L6.81833 10.8182C6.6426 10.994 6.35767 10.994 6.18194 10.8182C6.0062 10.6425 6.0062 10.3576 6.18194 10.1819L8.86374 7.50005L6.18194 4.81825C6.0062 4.64251 6.0062 4.35759 6.18194 4.18185Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/caret-sort.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.93179 5.43179C4.75605 5.60753 4.75605 5.89245 4.93179 6.06819C5.10753 6.24392 5.39245 6.24392 5.56819 6.06819L7.49999 4.13638L9.43179 6.06819C9.60753 6.24392 9.89245 6.24392 10.0682 6.06819C10.2439 5.89245 10.2439 5.60753 10.0682 5.43179L7.81819 3.18179C7.73379 3.0974 7.61933 3.04999 7.49999 3.04999C7.38064 3.04999 7.26618 3.0974 7.18179 3.18179L4.93179 5.43179ZM10.0682 9.56819C10.2439 9.39245 10.2439 9.10753 10.0682 8.93179C9.89245 8.75606 9.60753 8.75606 9.43179 8.93179L7.49999 10.8636L5.56819 8.93179C5.39245 8.75606 5.10753 8.75606 4.93179 8.93179C4.75605 9.10753 4.75605 9.39245 4.93179 9.56819L7.18179 11.8182C7.35753 11.9939 7.64245 11.9939 7.81819 11.8182L10.0682 9.56819Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/caret-up.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.18179 8.81819C4.00605 8.64245 4.00605 8.35753 4.18179 8.18179L7.18179 5.18179C7.26618 5.0974 7.38064 5.04999 7.49999 5.04999C7.61933 5.04999 7.73379 5.0974 7.81819 5.18179L10.8182 8.18179C10.9939 8.35753 10.9939 8.64245 10.8182 8.81819C10.6424 8.99392 10.3575 8.99392 10.1818 8.81819L7.49999 6.13638L4.81819 8.81819C4.64245 8.99392 4.35753 8.99392 4.18179 8.81819Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/chat-bubble.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.5 3L2.5 3.00002C1.67157 3.00002 1 3.6716 1 4.50002V9.50003C1 10.3285 1.67157 11 2.5 11H7.50003C7.63264 11 7.75982 11.0527 7.85358 11.1465L10 13.2929V11.5C10 11.2239 10.2239 11 10.5 11H12.5C13.3284 11 14 10.3285 14 9.50003V4.5C14 3.67157 13.3284 3 12.5 3ZM2.49999 2.00002L12.5 2C13.8807 2 15 3.11929 15 4.5V9.50003C15 10.8807 13.8807 12 12.5 12H11V14.5C11 14.7022 10.8782 14.8845 10.6913 14.9619C10.5045 15.0393 10.2894 14.9965 10.1464 14.8536L7.29292 12H2.5C1.11929 12 0 10.8807 0 9.50003V4.50002C0 3.11931 1.11928 2.00003 2.49999 2.00002Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/check-circled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.877045C3.84222 0.877045 0.877075 3.84219 0.877075 7.49988C0.877075 11.1575 3.84222 14.1227 7.49991 14.1227C11.1576 14.1227 14.1227 11.1575 14.1227 7.49988C14.1227 3.84219 11.1576 0.877045 7.49991 0.877045ZM1.82708 7.49988C1.82708 4.36686 4.36689 1.82704 7.49991 1.82704C10.6329 1.82704 13.1727 4.36686 13.1727 7.49988C13.1727 10.6329 10.6329 13.1727 7.49991 13.1727C4.36689 13.1727 1.82708 10.6329 1.82708 7.49988ZM10.1589 5.53774C10.3178 5.31191 10.2636 5.00001 10.0378 4.84109C9.81194 4.68217 9.50004 4.73642 9.34112 4.96225L6.51977 8.97154L5.35681 7.78706C5.16334 7.59002 4.84677 7.58711 4.64973 7.78058C4.45268 7.97404 4.44978 8.29061 4.64325 8.48765L6.22658 10.1003C6.33054 10.2062 6.47617 10.2604 6.62407 10.2483C6.77197 10.2363 6.90686 10.1591 6.99226 10.0377L10.1589 5.53774Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/check.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/checkbox.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 3H12V12H3L3 3ZM2 3C2 2.44771 2.44772 2 3 2H12C12.5523 2 13 2.44772 13 3V12C13 12.5523 12.5523 13 12 13H3C2.44771 13 2 12.5523 2 12V3ZM10.3498 5.51105C10.506 5.28337 10.4481 4.97212 10.2204 4.81587C9.99275 4.65961 9.6815 4.71751 9.52525 4.94519L6.64048 9.14857L5.19733 7.40889C5.02102 7.19635 4.7058 7.16699 4.49327 7.34329C4.28073 7.5196 4.25137 7.83482 4.42767 8.04735L6.2934 10.2964C6.39348 10.4171 6.54437 10.4838 6.70097 10.4767C6.85757 10.4695 7.00177 10.3894 7.09047 10.2601L10.3498 5.51105Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/chevron-down.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/chevron-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/chevron-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/chevron-up.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.13523 8.84197C3.3241 9.04343 3.64052 9.05363 3.84197 8.86477L7.5 5.43536L11.158 8.86477C11.3595 9.05363 11.6759 9.04343 11.8648 8.84197C12.0536 8.64051 12.0434 8.32409 11.842 8.13523L7.84197 4.38523C7.64964 4.20492 7.35036 4.20492 7.15803 4.38523L3.15803 8.13523C2.95657 8.32409 2.94637 8.64051 3.13523 8.84197Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/circle-backslash.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.877075C3.84222 0.877075 0.877075 3.84222 0.877075 7.49991C0.877075 11.1576 3.84222 14.1227 7.49991 14.1227C11.1576 14.1227 14.1227 11.1576 14.1227 7.49991C14.1227 3.84222 11.1576 0.877075 7.49991 0.877075ZM3.85768 3.15057C4.84311 2.32448 6.11342 1.82708 7.49991 1.82708C10.6329 1.82708 13.1727 4.36689 13.1727 7.49991C13.1727 8.88638 12.6753 10.1567 11.8492 11.1421L3.85768 3.15057ZM3.15057 3.85768C2.32448 4.84311 1.82708 6.11342 1.82708 7.49991C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C8.88638 13.1727 10.1567 12.6753 11.1421 11.8492L3.15057 3.85768Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/circle.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49991C0.877075 3.84222 3.84222 0.877075 7.49991 0.877075C11.1576 0.877075 14.1227 3.84222 14.1227 7.49991C14.1227 11.1576 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1576 0.877075 7.49991ZM7.49991 1.82708C4.36689 1.82708 1.82708 4.36689 1.82708 7.49991C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49991C13.1727 4.36689 10.6329 1.82708 7.49991 1.82708Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/clipboard.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5 2V1H10V2H5ZM4.75 0C4.33579 0 4 0.335786 4 0.75V1H3.5C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V2.5C13 1.67157 12.3284 1 11.5 1H11V0.75C11 0.335786 10.6642 0 10.25 0H4.75ZM11 2V2.25C11 2.66421 10.6642 3 10.25 3H4.75C4.33579 3 4 2.66421 4 2.25V2H3.5C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V2.5C12 2.22386 11.7761 2 11.5 2H11Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/clock.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.50009 0.877014C3.84241 0.877014 0.877258 3.84216 0.877258 7.49984C0.877258 11.1575 3.8424 14.1227 7.50009 14.1227C11.1578 14.1227 14.1229 11.1575 14.1229 7.49984C14.1229 3.84216 11.1577 0.877014 7.50009 0.877014ZM1.82726 7.49984C1.82726 4.36683 4.36708 1.82701 7.50009 1.82701C10.6331 1.82701 13.1729 4.36683 13.1729 7.49984C13.1729 10.6328 10.6331 13.1727 7.50009 13.1727C4.36708 13.1727 1.82726 10.6328 1.82726 7.49984ZM8 4.50001C8 4.22387 7.77614 4.00001 7.5 4.00001C7.22386 4.00001 7 4.22387 7 4.50001V7.50001C7 7.63262 7.05268 7.7598 7.14645 7.85357L9.14645 9.85357C9.34171 10.0488 9.65829 10.0488 9.85355 9.85357C10.0488 9.65831 10.0488 9.34172 9.85355 9.14646L8 7.29291V4.50001Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/code.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.96424 2.68571C10.0668 2.42931 9.94209 2.13833 9.6857 2.03577C9.4293 1.93322 9.13832 2.05792 9.03576 2.31432L5.03576 12.3143C4.9332 12.5707 5.05791 12.8617 5.3143 12.9642C5.5707 13.0668 5.86168 12.9421 5.96424 12.6857L9.96424 2.68571ZM3.85355 5.14646C4.04882 5.34172 4.04882 5.6583 3.85355 5.85356L2.20711 7.50001L3.85355 9.14646C4.04882 9.34172 4.04882 9.6583 3.85355 9.85356C3.65829 10.0488 3.34171 10.0488 3.14645 9.85356L1.14645 7.85356C0.951184 7.6583 0.951184 7.34172 1.14645 7.14646L3.14645 5.14646C3.34171 4.9512 3.65829 4.9512 3.85355 5.14646ZM11.1464 5.14646C11.3417 4.9512 11.6583 4.9512 11.8536 5.14646L13.8536 7.14646C14.0488 7.34172 14.0488 7.6583 13.8536 7.85356L11.8536 9.85356C11.6583 10.0488 11.3417 10.0488 11.1464 9.85356C10.9512 9.6583 10.9512 9.34172 11.1464 9.14646L12.7929 7.50001L11.1464 5.85356C10.9512 5.6583 10.9512 5.34172 11.1464 5.14646Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/color-wheel.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49985C0.877075 3.84216 3.84222 0.877014 7.49991 0.877014C11.1576 0.877014 14.1227 3.84216 14.1227 7.49985C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49985ZM3.78135 3.21565C4.68298 2.43239 5.83429 1.92904 7.09998 1.84089V6.53429L3.78135 3.21565ZM3.21567 3.78134C2.43242 4.68298 1.92909 5.83428 1.84095 7.09997H6.5343L3.21567 3.78134ZM6.5343 7.89997H1.84097C1.92916 9.16562 2.43253 10.3169 3.21579 11.2185L6.5343 7.89997ZM3.78149 11.7842C4.6831 12.5673 5.83435 13.0707 7.09998 13.1588V8.46566L3.78149 11.7842ZM7.89998 8.46566V13.1588C9.16559 13.0706 10.3168 12.5673 11.2184 11.7841L7.89998 8.46566ZM11.7841 11.2184C12.5673 10.3168 13.0707 9.16558 13.1588 7.89997H8.46567L11.7841 11.2184ZM8.46567 7.09997H13.1589C13.0707 5.83432 12.5674 4.68305 11.7842 3.78143L8.46567 7.09997ZM11.2185 3.21573C10.3169 2.43246 9.16565 1.92909 7.89998 1.8409V6.53429L11.2185 3.21573Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/columns.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.14998 14V1H0.849976V14H2.14998ZM6.14998 14V1H4.84998V14H6.14998ZM10.15 1V14H8.84998V1H10.15ZM14.15 14V1H12.85V14H14.15Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/commit.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.94969 7.49989C9.94969 8.85288 8.85288 9.94969 7.49989 9.94969C6.14691 9.94969 5.0501 8.85288 5.0501 7.49989C5.0501 6.14691 6.14691 5.0501 7.49989 5.0501C8.85288 5.0501 9.94969 6.14691 9.94969 7.49989ZM10.8632 8C10.6213 9.64055 9.20764 10.8997 7.49989 10.8997C5.79214 10.8997 4.37847 9.64055 4.13662 8H0.5C0.223858 8 0 7.77614 0 7.5C0 7.22386 0.223858 7 0.5 7H4.13659C4.37835 5.35935 5.79206 4.1001 7.49989 4.1001C9.20772 4.1001 10.6214 5.35935 10.8632 7H14.5C14.7761 7 15 7.22386 15 7.5C15 7.77614 14.7761 8 14.5 8H10.8632Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/component-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/component-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/component-boolean.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.85367 1.48956C7.65841 1.29429 7.34182 1.29429 7.14656 1.48956L1.48971 7.14641C1.29445 7.34167 1.29445 7.65825 1.48971 7.85352L7.14656 13.5104C7.34182 13.7056 7.65841 13.7056 7.85367 13.5104L13.5105 7.85352C13.7058 7.65825 13.7058 7.34167 13.5105 7.14641L7.85367 1.48956ZM7.5 2.55033L2.55037 7.49996L7.5 12.4496V2.55033Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/component-instance.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.1465 1.48959C7.34176 1.29432 7.65835 1.29432 7.85361 1.48959L13.5105 7.14644C13.7057 7.3417 13.7057 7.65829 13.5105 7.85355L7.85361 13.5104C7.65835 13.7057 7.34176 13.7057 7.1465 13.5104L1.48965 7.85355C1.29439 7.65829 1.29439 7.3417 1.48965 7.14644L7.1465 1.48959ZM7.50005 2.55025L2.55031 7.49999L7.50005 12.4497L12.4498 7.49999L7.50005 2.55025Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/component-none.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.85361 1.48959C7.65835 1.29432 7.34176 1.29432 7.1465 1.48959L1.48965 7.14644C1.29439 7.3417 1.29439 7.65829 1.48965 7.85355L3.9645 10.3284L1.64644 12.6464C1.45118 12.8417 1.45118 13.1583 1.64644 13.3536C1.84171 13.5488 2.15829 13.5488 2.35355 13.3536L4.6716 11.0355L7.1465 13.5104C7.34176 13.7057 7.65835 13.7057 7.85361 13.5104L13.5105 7.85355C13.7057 7.65829 13.7057 7.3417 13.5105 7.14644L11.0356 4.67154L13.3535 2.35355C13.5488 2.15829 13.5488 1.84171 13.3535 1.64645C13.1583 1.45118 12.8417 1.45118 12.6464 1.64645L10.3285 3.96443L7.85361 1.48959ZM9.62135 4.67154L7.50005 2.55025L2.55031 7.49999L4.6716 9.62129L9.62135 4.67154ZM5.37871 10.3284L7.50005 12.4497L12.4498 7.49999L10.3285 5.37865L5.37871 10.3284Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/container.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/cookie.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/copy.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 9.50006C1 10.3285 1.67157 11.0001 2.5 11.0001H4L4 10.0001H2.5C2.22386 10.0001 2 9.7762 2 9.50006L2 2.50006C2 2.22392 2.22386 2.00006 2.5 2.00006L9.5 2.00006C9.77614 2.00006 10 2.22392 10 2.50006V4.00002H5.5C4.67158 4.00002 4 4.67159 4 5.50002V12.5C4 13.3284 4.67158 14 5.5 14H12.5C13.3284 14 14 13.3284 14 12.5V5.50002C14 4.67159 13.3284 4.00002 12.5 4.00002H11V2.50006C11 1.67163 10.3284 1.00006 9.5 1.00006H2.5C1.67157 1.00006 1 1.67163 1 2.50006V9.50006ZM5 5.50002C5 5.22388 5.22386 5.00002 5.5 5.00002H12.5C12.7761 5.00002 13 5.22388 13 5.50002V12.5C13 12.7762 12.7761 13 12.5 13H5.5C5.22386 13 5 12.7762 5 12.5V5.50002Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/corner-bottom-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.87737 12H9.9H11.5C11.7761 12 12 11.7761 12 11.5C12 11.2239 11.7761 11 11.5 11H9.9C8.77164 11 7.95545 10.9996 7.31352 10.9472C6.67744 10.8952 6.25662 10.7946 5.91103 10.6185C5.25247 10.283 4.71703 9.74753 4.38148 9.08897C4.20539 8.74338 4.10481 8.32256 4.05284 7.68648C4.00039 7.04455 4 6.22836 4 5.1V3.5C4 3.22386 3.77614 3 3.5 3C3.22386 3 3 3.22386 3 3.5V5.1V5.12263C3 6.22359 3 7.08052 3.05616 7.76791C3.11318 8.46584 3.23058 9.0329 3.49047 9.54296C3.9219 10.3897 4.61031 11.0781 5.45704 11.5095C5.9671 11.7694 6.53416 11.8868 7.23209 11.9438C7.91948 12 8.77641 12 9.87737 12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/corner-bottom-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.12263 12H5.1H3.5C3.22386 12 3 11.7761 3 11.5C3 11.2239 3.22386 11 3.5 11H5.1C6.22836 11 7.04455 10.9996 7.68648 10.9472C8.32256 10.8952 8.74338 10.7946 9.08897 10.6185C9.74753 10.283 10.283 9.74753 10.6185 9.08897C10.7946 8.74338 10.8952 8.32256 10.9472 7.68648C10.9996 7.04455 11 6.22836 11 5.1V3.5C11 3.22386 11.2239 3 11.5 3C11.7761 3 12 3.22386 12 3.5V5.1V5.12263C12 6.22359 12 7.08052 11.9438 7.76791C11.8868 8.46584 11.7694 9.0329 11.5095 9.54296C11.0781 10.3897 10.3897 11.0781 9.54296 11.5095C9.0329 11.7694 8.46584 11.8868 7.76791 11.9438C7.08052 12 6.22359 12 5.12263 12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/corner-top-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.87737 3H9.9H11.5C11.7761 3 12 3.22386 12 3.5C12 3.77614 11.7761 4 11.5 4H9.9C8.77164 4 7.95545 4.00039 7.31352 4.05284C6.67744 4.10481 6.25662 4.20539 5.91103 4.38148C5.25247 4.71703 4.71703 5.25247 4.38148 5.91103C4.20539 6.25662 4.10481 6.67744 4.05284 7.31352C4.00039 7.95545 4 8.77164 4 9.9V11.5C4 11.7761 3.77614 12 3.5 12C3.22386 12 3 11.7761 3 11.5V9.9V9.87737C3 8.77641 3 7.91948 3.05616 7.23209C3.11318 6.53416 3.23058 5.9671 3.49047 5.45704C3.9219 4.61031 4.61031 3.9219 5.45704 3.49047C5.9671 3.23058 6.53416 3.11318 7.23209 3.05616C7.91948 3 8.77641 3 9.87737 3Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/corner-top-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.12263 3H5.1H3.5C3.22386 3 3 3.22386 3 3.5C3 3.77614 3.22386 4 3.5 4H5.1C6.22836 4 7.04455 4.00039 7.68648 4.05284C8.32256 4.10481 8.74338 4.20539 9.08897 4.38148C9.74753 4.71703 10.283 5.25247 10.6185 5.91103C10.7946 6.25662 10.8952 6.67744 10.9472 7.31352C10.9996 7.95545 11 8.77164 11 9.9V11.5C11 11.7761 11.2239 12 11.5 12C11.7761 12 12 11.7761 12 11.5V9.9V9.87737C12 8.77641 12 7.91948 11.9438 7.23209C11.8868 6.53416 11.7694 5.9671 11.5095 5.45704C11.0781 4.61031 10.3897 3.9219 9.54296 3.49047C9.0329 3.23058 8.46584 3.11318 7.76791 3.05616C7.08052 3 6.22359 3 5.12263 3Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/corners.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/countdown-timer.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.15 7.49998C13.15 4.66458 10.9402 1.84998 7.50002 1.84998C4.7217 1.84998 3.34851 3.90636 2.76336 4.99997H4.5C4.77614 4.99997 5 5.22383 5 5.49997C5 5.77611 4.77614 5.99997 4.5 5.99997H1.5C1.22386 5.99997 1 5.77611 1 5.49997V2.49997C1 2.22383 1.22386 1.99997 1.5 1.99997C1.77614 1.99997 2 2.22383 2 2.49997V4.31318C2.70453 3.07126 4.33406 0.849976 7.50002 0.849976C11.5628 0.849976 14.15 4.18537 14.15 7.49998C14.15 10.8146 11.5628 14.15 7.50002 14.15C5.55618 14.15 3.93778 13.3808 2.78548 12.2084C2.16852 11.5806 1.68668 10.839 1.35816 10.0407C1.25306 9.78536 1.37488 9.49315 1.63024 9.38806C1.8856 9.28296 2.17781 9.40478 2.2829 9.66014C2.56374 10.3425 2.97495 10.9745 3.4987 11.5074C4.47052 12.4963 5.83496 13.15 7.50002 13.15C10.9402 13.15 13.15 10.3354 13.15 7.49998ZM7 10V5.00001H8V10H7Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/counter-clockwise-clock.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.15 7.49998C13.15 4.66458 10.9402 1.84998 7.50002 1.84998C4.72167 1.84998 3.34849 3.9064 2.76335 5H4.5C4.77614 5 5 5.22386 5 5.5C5 5.77614 4.77614 6 4.5 6H1.5C1.22386 6 1 5.77614 1 5.5V2.5C1 2.22386 1.22386 2 1.5 2C1.77614 2 2 2.22386 2 2.5V4.31318C2.70453 3.07126 4.33406 0.849976 7.50002 0.849976C11.5628 0.849976 14.15 4.18537 14.15 7.49998C14.15 10.8146 11.5628 14.15 7.50002 14.15C5.55618 14.15 3.93778 13.3808 2.78548 12.2084C2.16852 11.5806 1.68668 10.839 1.35816 10.0407C1.25306 9.78536 1.37488 9.49315 1.63024 9.38806C1.8856 9.28296 2.17781 9.40478 2.2829 9.66014C2.56374 10.3425 2.97495 10.9745 3.4987 11.5074C4.47052 12.4963 5.83496 13.15 7.50002 13.15C10.9402 13.15 13.15 10.3354 13.15 7.49998ZM7.5 4.00001C7.77614 4.00001 8 4.22387 8 4.50001V7.29291L9.85355 9.14646C10.0488 9.34172 10.0488 9.65831 9.85355 9.85357C9.65829 10.0488 9.34171 10.0488 9.14645 9.85357L7.14645 7.85357C7.05268 7.7598 7 7.63262 7 7.50001V4.50001C7 4.22387 7.22386 4.00001 7.5 4.00001Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/crop.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.5 8.00684e-07C3.77614 7.88614e-07 4 0.223859 4 0.500001L4 3.00006L11.5 3.00006C11.7761 3.00006 12 3.22392 12 3.50006L12 11.0001L14.5 11C14.7761 11 15 11.2238 15 11.5C15 11.7761 14.7762 12 14.5 12L12 12.0001L12 14.5C12 14.7761 11.7761 15 11.5 15C11.2239 15 11 14.7761 11 14.5L11 12.0001L3.5 12.0001C3.22386 12.0001 3 11.7762 3 11.5001L3 4.00005L0.499989 4C0.223847 4 -6.10541e-06 3.77613 -5.02576e-07 3.49999C5.13006e-06 3.22385 0.223867 3 0.50001 3L3 3.00005L3 0.500001C3 0.223859 3.22386 8.12755e-07 3.5 8.00684e-07ZM4 4.00006L4 11.0001L11 11.0001L11 4.00006L4 4.00006Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cross-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.8536 2.85355C13.0488 2.65829 13.0488 2.34171 12.8536 2.14645C12.6583 1.95118 12.3417 1.95118 12.1464 2.14645L7.5 6.79289L2.85355 2.14645C2.65829 1.95118 2.34171 1.95118 2.14645 2.14645C1.95118 2.34171 1.95118 2.65829 2.14645 2.85355L6.79289 7.5L2.14645 12.1464C1.95118 12.3417 1.95118 12.6583 2.14645 12.8536C2.34171 13.0488 2.65829 13.0488 2.85355 12.8536L7.5 8.20711L12.1464 12.8536C12.3417 13.0488 12.6583 13.0488 12.8536 12.8536C13.0488 12.6583 13.0488 12.3417 12.8536 12.1464L8.20711 7.5L12.8536 2.85355Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cross-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cross-circled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49988C0.877075 3.84219 3.84222 0.877045 7.49991 0.877045C11.1576 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49988ZM7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 4.36686 10.6329 1.82704 7.49991 1.82704ZM9.85358 5.14644C10.0488 5.3417 10.0488 5.65829 9.85358 5.85355L8.20713 7.49999L9.85358 9.14644C10.0488 9.3417 10.0488 9.65829 9.85358 9.85355C9.65832 10.0488 9.34173 10.0488 9.14647 9.85355L7.50002 8.2071L5.85358 9.85355C5.65832 10.0488 5.34173 10.0488 5.14647 9.85355C4.95121 9.65829 4.95121 9.3417 5.14647 9.14644L6.79292 7.49999L5.14647 5.85355C4.95121 5.65829 4.95121 5.3417 5.14647 5.14644C5.34173 4.95118 5.65832 4.95118 5.85358 5.14644L7.50002 6.79289L9.14647 5.14644C9.34173 4.95118 9.65832 4.95118 9.85358 5.14644Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/crosshair-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.50207C0.877075 3.84319 3.84319 0.877075 7.50208 0.877075C11.1609 0.877075 14.1271 3.84319 14.1271 7.50207C14.1271 11.1609 11.1609 14.1271 7.50208 14.1271C3.84319 14.1271 0.877075 11.1609 0.877075 7.50207ZM1.84898 7.00003C2.0886 4.26639 4.26639 2.0886 7.00003 1.84898V4.50003C7.00003 4.77617 7.22388 5.00003 7.50003 5.00003C7.77617 5.00003 8.00003 4.77617 8.00003 4.50003V1.84862C10.7356 2.08643 12.9154 4.26502 13.1552 7.00003H10.5C10.2239 7.00003 10 7.22388 10 7.50003C10 7.77617 10.2239 8.00003 10.5 8.00003H13.1555C12.9176 10.7369 10.7369 12.9176 8.00003 13.1555V10.5C8.00003 10.2239 7.77617 10 7.50003 10C7.22388 10 7.00003 10.2239 7.00003 10.5V13.1552C4.26502 12.9154 2.08643 10.7356 1.84862 8.00003H4.50003C4.77617 8.00003 5.00003 7.77617 5.00003 7.50003C5.00003 7.22388 4.77617 7.00003 4.50003 7.00003H1.84898Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/crosshair-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 0C7.77614 0 8 0.223858 8 0.5V1.80687C10.6922 2.0935 12.8167 4.28012 13.0068 7H14.5C14.7761 7 15 7.22386 15 7.5C15 7.77614 14.7761 8 14.5 8H12.9888C12.7094 10.6244 10.6244 12.7094 8 12.9888V14.5C8 14.7761 7.77614 15 7.5 15C7.22386 15 7 14.7761 7 14.5V13.0068C4.28012 12.8167 2.0935 10.6922 1.80687 8H0.5C0.223858 8 0 7.77614 0 7.5C0 7.22386 0.223858 7 0.5 7H1.78886C1.98376 4.21166 4.21166 1.98376 7 1.78886V0.5C7 0.223858 7.22386 0 7.5 0ZM8 12.0322V9.5C8 9.22386 7.77614 9 7.5 9C7.22386 9 7 9.22386 7 9.5V12.054C4.80517 11.8689 3.04222 10.1668 2.76344 8H5.5C5.77614 8 6 7.77614 6 7.5C6 7.22386 5.77614 7 5.5 7H2.7417C2.93252 4.73662 4.73662 2.93252 7 2.7417V5.5C7 5.77614 7.22386 6 7.5 6C7.77614 6 8 5.77614 8 5.5V2.76344C10.1668 3.04222 11.8689 4.80517 12.054 7H9.5C9.22386 7 9 7.22386 9 7.5C9 7.77614 9.22386 8 9.5 8H12.0322C11.7621 10.0991 10.0991 11.7621 8 12.0322Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cube.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.28856 0.796908C7.42258 0.734364 7.57742 0.734364 7.71144 0.796908L13.7114 3.59691C13.8875 3.67906 14 3.85574 14 4.05V10.95C14 11.1443 13.8875 11.3209 13.7114 11.4031L7.71144 14.2031C7.57742 14.2656 7.42258 14.2656 7.28856 14.2031L1.28856 11.4031C1.11252 11.3209 1 11.1443 1 10.95V4.05C1 3.85574 1.11252 3.67906 1.28856 3.59691L7.28856 0.796908ZM2 4.80578L7 6.93078V12.9649L2 10.6316V4.80578ZM8 12.9649L13 10.6316V4.80578L8 6.93078V12.9649ZM7.5 6.05672L12.2719 4.02866L7.5 1.80176L2.72809 4.02866L7.5 6.05672Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cursor-arrow.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.29227 0.048984C3.47033 -0.032338 3.67946 -0.00228214 3.8274 0.125891L12.8587 7.95026C13.0134 8.08432 13.0708 8.29916 13.0035 8.49251C12.9362 8.68586 12.7578 8.81866 12.5533 8.82768L9.21887 8.97474L11.1504 13.2187C11.2648 13.47 11.1538 13.7664 10.9026 13.8808L8.75024 14.8613C8.499 14.9758 8.20255 14.8649 8.08802 14.6137L6.15339 10.3703L3.86279 12.7855C3.72196 12.934 3.50487 12.9817 3.31479 12.9059C3.1247 12.8301 3 12.6461 3 12.4414V0.503792C3 0.308048 3.11422 0.130306 3.29227 0.048984ZM4 1.59852V11.1877L5.93799 9.14425C6.05238 9.02363 6.21924 8.96776 6.38319 8.99516C6.54715 9.02256 6.68677 9.12965 6.75573 9.2809L8.79056 13.7441L10.0332 13.178L8.00195 8.71497C7.93313 8.56376 7.94391 8.38824 8.03072 8.24659C8.11753 8.10494 8.26903 8.01566 8.435 8.00834L11.2549 7.88397L4 1.59852Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/cursor-text.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/dash.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5 7.5C5 7.22386 5.22386 7 5.5 7H9.5C9.77614 7 10 7.22386 10 7.5C10 7.77614 9.77614 8 9.5 8H5.5C5.22386 8 5 7.77614 5 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dashboard.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/desktop-mute.svg πŸ”—

@@ -0,0 +1,4 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.73284 2H1.25C0.559643 2 0 2.55964 0 3.25V10.75C0 11.249 0.292407 11.6797 0.715228 11.8802C0.729901 11.8616 0.74533 11.8435 0.761518 11.8257L1.51545 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25C1 3.11193 1.11193 3 1.25 3H8.8198L9.73284 2ZM13.5232 3L14.316 2.13518C14.7219 2.34168 15 2.76336 15 3.25V10.75C15 11.4404 14.4404 12 13.75 12H9.92659L10.1701 13.2986C10.2336 13.6371 9.97389 13.95 9.62951 13.95H5.37049C5.02612 13.95 4.76645 13.6371 4.82991 13.2986L5.02202 12.2741L6.18991 11H13.75C13.8881 11 14 10.8881 14 10.75V3.25C14 3.11193 13.8881 3 13.75 3H13.5232ZM5.98909 12H9.01091L9.20778 13.05H5.79222L5.98909 12Z" fill="black"/>
+<path d="M13 1L2 13" stroke="black" stroke-linecap="round"/>
+</svg>

assets/icons/radix/desktop.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 3.25C1 3.11193 1.11193 3 1.25 3H13.75C13.8881 3 14 3.11193 14 3.25V10.75C14 10.8881 13.8881 11 13.75 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25ZM1.25 2C0.559643 2 0 2.55964 0 3.25V10.75C0 11.4404 0.559644 12 1.25 12H5.07341L4.82991 13.2986C4.76645 13.6371 5.02612 13.95 5.37049 13.95H9.62951C9.97389 13.95 10.2336 13.6371 10.1701 13.2986L9.92659 12H13.75C14.4404 12 15 11.4404 15 10.75V3.25C15 2.55964 14.4404 2 13.75 2H1.25ZM9.01091 12H5.98909L5.79222 13.05H9.20778L9.01091 12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dimensions.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/disc.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.877075C3.84222 0.877075 0.877075 3.84222 0.877075 7.49991C0.877075 11.1576 3.84222 14.1227 7.49991 14.1227C11.1576 14.1227 14.1227 11.1576 14.1227 7.49991C14.1227 3.84222 11.1576 0.877075 7.49991 0.877075ZM1.82708 7.49991C1.82708 4.36689 4.36689 1.82707 7.49991 1.82707C10.6329 1.82707 13.1727 4.36689 13.1727 7.49991C13.1727 10.6329 10.6329 13.1727 7.49991 13.1727C4.36689 13.1727 1.82708 10.6329 1.82708 7.49991ZM8.37287 7.50006C8.37287 7.98196 7.98221 8.37263 7.5003 8.37263C7.01839 8.37263 6.62773 7.98196 6.62773 7.50006C6.62773 7.01815 7.01839 6.62748 7.5003 6.62748C7.98221 6.62748 8.37287 7.01815 8.37287 7.50006ZM9.32287 7.50006C9.32287 8.50664 8.50688 9.32263 7.5003 9.32263C6.49372 9.32263 5.67773 8.50664 5.67773 7.50006C5.67773 6.49348 6.49372 5.67748 7.5003 5.67748C8.50688 5.67748 9.32287 6.49348 9.32287 7.50006Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/discord-logo.svg πŸ”—

@@ -0,0 +1,13 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g clip-path="url(#clip0_16200_18)">
+    <path
+      fill-rule="evenodd"
+      clip-rule="evenodd"

assets/icons/radix/divider-horizontal.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 7.5C2 7.22386 2.22386 7 2.5 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H2.5C2.22386 8 2 7.77614 2 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/divider-vertical.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 2C7.77614 2 8 2.22386 8 2.5L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 2.5C7 2.22386 7.22386 2 7.5 2Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dot-filled.svg πŸ”—

@@ -0,0 +1,6 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    d="M9.875 7.5C9.875 8.81168 8.81168 9.875 7.5 9.875C6.18832 9.875 5.125 8.81168 5.125 7.5C5.125 6.18832 6.18832 5.125 7.5 5.125C8.81168 5.125 9.875 6.18832 9.875 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dot-solid.svg πŸ”—

@@ -0,0 +1,6 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    d="M9.875 7.5C9.875 8.81168 8.81168 9.875 7.5 9.875C6.18832 9.875 5.125 8.81168 5.125 7.5C5.125 6.18832 6.18832 5.125 7.5 5.125C8.81168 5.125 9.875 6.18832 9.875 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dot.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 9.125C8.39746 9.125 9.125 8.39746 9.125 7.5C9.125 6.60254 8.39746 5.875 7.5 5.875C6.60254 5.875 5.875 6.60254 5.875 7.5C5.875 8.39746 6.60254 9.125 7.5 9.125ZM7.5 10.125C8.94975 10.125 10.125 8.94975 10.125 7.5C10.125 6.05025 8.94975 4.875 7.5 4.875C6.05025 4.875 4.875 6.05025 4.875 7.5C4.875 8.94975 6.05025 10.125 7.5 10.125Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dots-horizontal.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.625 7.5C3.625 8.12132 3.12132 8.625 2.5 8.625C1.87868 8.625 1.375 8.12132 1.375 7.5C1.375 6.87868 1.87868 6.375 2.5 6.375C3.12132 6.375 3.625 6.87868 3.625 7.5ZM8.625 7.5C8.625 8.12132 8.12132 8.625 7.5 8.625C6.87868 8.625 6.375 8.12132 6.375 7.5C6.375 6.87868 6.87868 6.375 7.5 6.375C8.12132 6.375 8.625 6.87868 8.625 7.5ZM12.5 8.625C13.1213 8.625 13.625 8.12132 13.625 7.5C13.625 6.87868 13.1213 6.375 12.5 6.375C11.8787 6.375 11.375 6.87868 11.375 7.5C11.375 8.12132 11.8787 8.625 12.5 8.625Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dots-vertical.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.625 2.5C8.625 3.12132 8.12132 3.625 7.5 3.625C6.87868 3.625 6.375 3.12132 6.375 2.5C6.375 1.87868 6.87868 1.375 7.5 1.375C8.12132 1.375 8.625 1.87868 8.625 2.5ZM8.625 7.5C8.625 8.12132 8.12132 8.625 7.5 8.625C6.87868 8.625 6.375 8.12132 6.375 7.5C6.375 6.87868 6.87868 6.375 7.5 6.375C8.12132 6.375 8.625 6.87868 8.625 7.5ZM7.5 13.625C8.12132 13.625 8.625 13.1213 8.625 12.5C8.625 11.8787 8.12132 11.375 7.5 11.375C6.87868 11.375 6.375 11.8787 6.375 12.5C6.375 13.1213 6.87868 13.625 7.5 13.625Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/double-arrow-down.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.85355 2.14645C3.65829 1.95118 3.34171 1.95118 3.14645 2.14645C2.95118 2.34171 2.95118 2.65829 3.14645 2.85355L7.14645 6.85355C7.34171 7.04882 7.65829 7.04882 7.85355 6.85355L11.8536 2.85355C12.0488 2.65829 12.0488 2.34171 11.8536 2.14645C11.6583 1.95118 11.3417 1.95118 11.1464 2.14645L7.5 5.79289L3.85355 2.14645ZM3.85355 8.14645C3.65829 7.95118 3.34171 7.95118 3.14645 8.14645C2.95118 8.34171 2.95118 8.65829 3.14645 8.85355L7.14645 12.8536C7.34171 13.0488 7.65829 13.0488 7.85355 12.8536L11.8536 8.85355C12.0488 8.65829 12.0488 8.34171 11.8536 8.14645C11.6583 7.95118 11.3417 7.95118 11.1464 8.14645L7.5 11.7929L3.85355 8.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/double-arrow-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.85355 3.85355C7.04882 3.65829 7.04882 3.34171 6.85355 3.14645C6.65829 2.95118 6.34171 2.95118 6.14645 3.14645L2.14645 7.14645C1.95118 7.34171 1.95118 7.65829 2.14645 7.85355L6.14645 11.8536C6.34171 12.0488 6.65829 12.0488 6.85355 11.8536C7.04882 11.6583 7.04882 11.3417 6.85355 11.1464L3.20711 7.5L6.85355 3.85355ZM12.8536 3.85355C13.0488 3.65829 13.0488 3.34171 12.8536 3.14645C12.6583 2.95118 12.3417 2.95118 12.1464 3.14645L8.14645 7.14645C7.95118 7.34171 7.95118 7.65829 8.14645 7.85355L12.1464 11.8536C12.3417 12.0488 12.6583 12.0488 12.8536 11.8536C13.0488 11.6583 13.0488 11.3417 12.8536 11.1464L9.20711 7.5L12.8536 3.85355Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/double-arrow-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.14645 11.1464C1.95118 11.3417 1.95118 11.6583 2.14645 11.8536C2.34171 12.0488 2.65829 12.0488 2.85355 11.8536L6.85355 7.85355C7.04882 7.65829 7.04882 7.34171 6.85355 7.14645L2.85355 3.14645C2.65829 2.95118 2.34171 2.95118 2.14645 3.14645C1.95118 3.34171 1.95118 3.65829 2.14645 3.85355L5.79289 7.5L2.14645 11.1464ZM8.14645 11.1464C7.95118 11.3417 7.95118 11.6583 8.14645 11.8536C8.34171 12.0488 8.65829 12.0488 8.85355 11.8536L12.8536 7.85355C13.0488 7.65829 13.0488 7.34171 12.8536 7.14645L8.85355 3.14645C8.65829 2.95118 8.34171 2.95118 8.14645 3.14645C7.95118 3.34171 7.95118 3.65829 8.14645 3.85355L11.7929 7.5L8.14645 11.1464Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/double-arrow-up.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.1464 6.85355C11.3417 7.04882 11.6583 7.04882 11.8536 6.85355C12.0488 6.65829 12.0488 6.34171 11.8536 6.14645L7.85355 2.14645C7.65829 1.95118 7.34171 1.95118 7.14645 2.14645L3.14645 6.14645C2.95118 6.34171 2.95118 6.65829 3.14645 6.85355C3.34171 7.04882 3.65829 7.04882 3.85355 6.85355L7.5 3.20711L11.1464 6.85355ZM11.1464 12.8536C11.3417 13.0488 11.6583 13.0488 11.8536 12.8536C12.0488 12.6583 12.0488 12.3417 11.8536 12.1464L7.85355 8.14645C7.65829 7.95118 7.34171 7.95118 7.14645 8.14645L3.14645 12.1464C2.95118 12.3417 2.95118 12.6583 3.14645 12.8536C3.34171 13.0488 3.65829 13.0488 3.85355 12.8536L7.5 9.20711L11.1464 12.8536Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/download.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.50005 1.04999C7.74858 1.04999 7.95005 1.25146 7.95005 1.49999V8.41359L10.1819 6.18179C10.3576 6.00605 10.6425 6.00605 10.8182 6.18179C10.994 6.35753 10.994 6.64245 10.8182 6.81819L7.81825 9.81819C7.64251 9.99392 7.35759 9.99392 7.18185 9.81819L4.18185 6.81819C4.00611 6.64245 4.00611 6.35753 4.18185 6.18179C4.35759 6.00605 4.64251 6.00605 4.81825 6.18179L7.05005 8.41359V1.49999C7.05005 1.25146 7.25152 1.04999 7.50005 1.04999ZM2.5 10C2.77614 10 3 10.2239 3 10.5V12C3 12.5539 3.44565 13 3.99635 13H11.0012C11.5529 13 12 12.5528 12 12V10.5C12 10.2239 12.2239 10 12.5 10C12.7761 10 13 10.2239 13 10.5V12C13 13.1041 12.1062 14 11.0012 14H3.99635C2.89019 14 2 13.103 2 12V10.5C2 10.2239 2.22386 10 2.5 10Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/drag-handle-dots-1.svg πŸ”—

@@ -0,0 +1,26 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <circle cx="4.5" cy="2.5" r=".6" fill="currentColor" />
+  <circle cx="4.5" cy="4.5" r=".6" fill="currentColor" />
+  <circle cx="4.5" cy="6.499" r=".6" fill="currentColor" />
+  <circle cx="4.5" cy="8.499" r=".6" fill="currentColor" />
+  <circle cx="4.5" cy="10.498" r=".6" fill="currentColor" />
+  <circle cx="4.5" cy="12.498" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="2.5" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="4.5" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="6.499" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="8.499" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="10.498" r=".6" fill="currentColor" />
+  <circle cx="6.5" cy="12.498" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="2.5" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="4.5" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="6.499" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="8.499" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="10.498" r=".6" fill="currentColor" />
+  <circle cx="8.499" cy="12.498" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="2.5" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="4.5" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="6.499" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="8.499" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="10.498" r=".6" fill="currentColor" />
+  <circle cx="10.499" cy="12.498" r=".6" fill="currentColor" />
+</svg>

assets/icons/radix/drag-handle-horizontal.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.49998 4.09998C2.27906 4.09998 2.09998 4.27906 2.09998 4.49998C2.09998 4.72089 2.27906 4.89998 2.49998 4.89998H12.5C12.7209 4.89998 12.9 4.72089 12.9 4.49998C12.9 4.27906 12.7209 4.09998 12.5 4.09998H2.49998ZM2.49998 6.09998C2.27906 6.09998 2.09998 6.27906 2.09998 6.49998C2.09998 6.72089 2.27906 6.89998 2.49998 6.89998H12.5C12.7209 6.89998 12.9 6.72089 12.9 6.49998C12.9 6.27906 12.7209 6.09998 12.5 6.09998H2.49998ZM2.09998 8.49998C2.09998 8.27906 2.27906 8.09998 2.49998 8.09998H12.5C12.7209 8.09998 12.9 8.27906 12.9 8.49998C12.9 8.72089 12.7209 8.89998 12.5 8.89998H2.49998C2.27906 8.89998 2.09998 8.72089 2.09998 8.49998ZM2.49998 10.1C2.27906 10.1 2.09998 10.2791 2.09998 10.5C2.09998 10.7209 2.27906 10.9 2.49998 10.9H12.5C12.7209 10.9 12.9 10.7209 12.9 10.5C12.9 10.2791 12.7209 10.1 12.5 10.1H2.49998Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/drag-handle-vertical.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.09998 12.5C4.09998 12.7209 4.27906 12.9 4.49998 12.9C4.72089 12.9 4.89998 12.7209 4.89998 12.5L4.89998 2.50002C4.89998 2.27911 4.72089 2.10003 4.49998 2.10003C4.27906 2.10003 4.09998 2.27911 4.09998 2.50002L4.09998 12.5ZM6.09998 12.5C6.09998 12.7209 6.27906 12.9 6.49998 12.9C6.72089 12.9 6.89998 12.7209 6.89998 12.5L6.89998 2.50002C6.89998 2.27911 6.72089 2.10003 6.49998 2.10003C6.27906 2.10003 6.09998 2.27911 6.09998 2.50002L6.09998 12.5ZM8.49998 12.9C8.27906 12.9 8.09998 12.7209 8.09998 12.5L8.09998 2.50002C8.09998 2.27911 8.27906 2.10002 8.49998 2.10002C8.72089 2.10002 8.89998 2.27911 8.89998 2.50002L8.89998 12.5C8.89998 12.7209 8.72089 12.9 8.49998 12.9ZM10.1 12.5C10.1 12.7209 10.2791 12.9 10.5 12.9C10.7209 12.9 10.9 12.7209 10.9 12.5L10.9 2.50002C10.9 2.27911 10.7209 2.10002 10.5 2.10002C10.2791 2.10002 10.1 2.27911 10.1 2.50002L10.1 12.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/drawing-pin-filled.svg πŸ”—

@@ -0,0 +1,14 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.62129 1.13607C9.81656 0.940808 10.1331 0.940809 10.3284 1.13607L11.3891 2.19673L12.8033 3.61094L13.8639 4.6716C14.0592 4.86687 14.0592 5.18345 13.8639 5.37871C13.6687 5.57397 13.3521 5.57397 13.1568 5.37871L12.5038 4.7257L8.86727 9.57443L9.97485 10.682C10.1701 10.8773 10.1701 11.1939 9.97485 11.3891C9.77959 11.5844 9.463 11.5844 9.26774 11.3891L7.85353 9.97491L6.79287 8.91425L3.5225 12.1846C3.32724 12.3799 3.01065 12.3799 2.81539 12.1846C2.62013 11.9894 2.62013 11.6728 2.81539 11.4775L6.08576 8.20714L5.0251 7.14648L3.61089 5.73226C3.41563 5.537 3.41562 5.22042 3.61089 5.02516C3.80615 4.8299 4.12273 4.8299 4.31799 5.02516L5.42557 6.13274L10.2743 2.49619L9.62129 1.84318C9.42603 1.64792 9.42603 1.33133 9.62129 1.13607Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.62129 1.13607C9.81656 0.940808 10.1331 0.940809 10.3284 1.13607L11.3891 2.19673L12.8033 3.61094L13.8639 4.6716C14.0592 4.86687 14.0592 5.18345 13.8639 5.37871C13.6687 5.57397 13.3521 5.57397 13.1568 5.37871L12.5038 4.7257L8.86727 9.57443L9.97485 10.682C10.1701 10.8773 10.1701 11.1939 9.97485 11.3891C9.77959 11.5844 9.463 11.5844 9.26774 11.3891L7.85353 9.97491L6.79287 8.91425L3.5225 12.1846C3.32724 12.3799 3.01065 12.3799 2.81539 12.1846C2.62013 11.9894 2.62013 11.6728 2.81539 11.4775L6.08576 8.20714L5.0251 7.14648L3.61089 5.73226C3.41563 5.537 3.41562 5.22042 3.61089 5.02516C3.80615 4.8299 4.12273 4.8299 4.31799 5.02516L5.42557 6.13274L10.2743 2.49619L9.62129 1.84318C9.42603 1.64792 9.42603 1.33133 9.62129 1.13607Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/drawing-pin-solid.svg πŸ”—

@@ -0,0 +1,14 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.62129 1.13607C9.81656 0.940808 10.1331 0.940809 10.3284 1.13607L11.3891 2.19673L12.8033 3.61094L13.8639 4.6716C14.0592 4.86687 14.0592 5.18345 13.8639 5.37871C13.6687 5.57397 13.3521 5.57397 13.1568 5.37871L12.5038 4.7257L8.86727 9.57443L9.97485 10.682C10.1701 10.8773 10.1701 11.1939 9.97485 11.3891C9.77959 11.5844 9.463 11.5844 9.26774 11.3891L7.85353 9.97491L6.79287 8.91425L3.5225 12.1846C3.32724 12.3799 3.01065 12.3799 2.81539 12.1846C2.62013 11.9894 2.62013 11.6728 2.81539 11.4775L6.08576 8.20714L5.0251 7.14648L3.61089 5.73226C3.41563 5.537 3.41562 5.22042 3.61089 5.02516C3.80615 4.8299 4.12273 4.8299 4.31799 5.02516L5.42557 6.13274L10.2743 2.49619L9.62129 1.84318C9.42603 1.64792 9.42603 1.33133 9.62129 1.13607Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.62129 1.13607C9.81656 0.940808 10.1331 0.940809 10.3284 1.13607L11.3891 2.19673L12.8033 3.61094L13.8639 4.6716C14.0592 4.86687 14.0592 5.18345 13.8639 5.37871C13.6687 5.57397 13.3521 5.57397 13.1568 5.37871L12.5038 4.7257L8.86727 9.57443L9.97485 10.682C10.1701 10.8773 10.1701 11.1939 9.97485 11.3891C9.77959 11.5844 9.463 11.5844 9.26774 11.3891L7.85353 9.97491L6.79287 8.91425L3.5225 12.1846C3.32724 12.3799 3.01065 12.3799 2.81539 12.1846C2.62013 11.9894 2.62013 11.6728 2.81539 11.4775L6.08576 8.20714L5.0251 7.14648L3.61089 5.73226C3.41563 5.537 3.41562 5.22042 3.61089 5.02516C3.80615 4.8299 4.12273 4.8299 4.31799 5.02516L5.42557 6.13274L10.2743 2.49619L9.62129 1.84318C9.42603 1.64792 9.42603 1.33133 9.62129 1.13607Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/drawing-pin.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.3285 1.13607C10.1332 0.940809 9.81662 0.940808 9.62136 1.13607C9.42609 1.33133 9.42609 1.64792 9.62136 1.84318L10.2744 2.49619L5.42563 6.13274L4.31805 5.02516C4.12279 4.8299 3.80621 4.8299 3.61095 5.02516C3.41569 5.22042 3.41569 5.537 3.61095 5.73226L5.02516 7.14648L6.08582 8.20714L2.81545 11.4775C2.62019 11.6728 2.62019 11.9894 2.81545 12.1846C3.01072 12.3799 3.3273 12.3799 3.52256 12.1846L6.79293 8.91425L7.85359 9.97491L9.2678 11.3891C9.46306 11.5844 9.77965 11.5844 9.97491 11.3891C10.1702 11.1939 10.1702 10.8773 9.97491 10.682L8.86733 9.57443L12.5039 4.7257L13.1569 5.37871C13.3522 5.57397 13.6687 5.57397 13.864 5.37871C14.0593 5.18345 14.0593 4.86687 13.864 4.6716L12.8033 3.61094L11.3891 2.19673L10.3285 1.13607ZM6.13992 6.84702L10.9887 3.21047L11.7896 4.01142L8.15305 8.86015L6.13992 6.84702Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/dropdown-menu.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49999 3.09998C7.27907 3.09998 7.09999 3.27906 7.09999 3.49998C7.09999 3.72089 7.27907 3.89998 7.49999 3.89998H14.5C14.7209 3.89998 14.9 3.72089 14.9 3.49998C14.9 3.27906 14.7209 3.09998 14.5 3.09998H7.49999ZM7.49998 5.1C7.27907 5.1 7.09998 5.27908 7.09998 5.5C7.09998 5.72091 7.27907 5.9 7.49998 5.9H14.5C14.7209 5.9 14.9 5.72091 14.9 5.5C14.9 5.27908 14.7209 5.1 14.5 5.1H7.49998ZM7.1 7.5C7.1 7.27908 7.27909 7.1 7.5 7.1H14.5C14.7209 7.1 14.9 7.27908 14.9 7.5C14.9 7.72091 14.7209 7.9 14.5 7.9H7.5C7.27909 7.9 7.1 7.72091 7.1 7.5ZM7.49998 9.1C7.27907 9.1 7.09998 9.27908 7.09998 9.5C7.09998 9.72091 7.27907 9.9 7.49998 9.9H14.5C14.7209 9.9 14.9 9.72091 14.9 9.5C14.9 9.27908 14.7209 9.1 14.5 9.1H7.49998ZM7.09998 11.5C7.09998 11.2791 7.27907 11.1 7.49998 11.1H14.5C14.7209 11.1 14.9 11.2791 14.9 11.5C14.9 11.7209 14.7209 11.9 14.5 11.9H7.49998C7.27907 11.9 7.09998 11.7209 7.09998 11.5ZM2.5 9.25003L5 6.00003H0L2.5 9.25003Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/enter-full-screen.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 2.5C2 2.22386 2.22386 2 2.5 2H5.5C5.77614 2 6 2.22386 6 2.5C6 2.77614 5.77614 3 5.5 3H3V5.5C3 5.77614 2.77614 6 2.5 6C2.22386 6 2 5.77614 2 5.5V2.5ZM9 2.5C9 2.22386 9.22386 2 9.5 2H12.5C12.7761 2 13 2.22386 13 2.5V5.5C13 5.77614 12.7761 6 12.5 6C12.2239 6 12 5.77614 12 5.5V3H9.5C9.22386 3 9 2.77614 9 2.5ZM2.5 9C2.77614 9 3 9.22386 3 9.5V12H5.5C5.77614 12 6 12.2239 6 12.5C6 12.7761 5.77614 13 5.5 13H2.5C2.22386 13 2 12.7761 2 12.5V9.5C2 9.22386 2.22386 9 2.5 9ZM12.5 9C12.7761 9 13 9.22386 13 9.5V12.5C13 12.7761 12.7761 13 12.5 13H9.5C9.22386 13 9 12.7761 9 12.5C9 12.2239 9.22386 12 9.5 12H12V9.5C12 9.22386 12.2239 9 12.5 9Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/enter.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.5 1C4.22386 1 4 1.22386 4 1.5C4 1.77614 4.22386 2 4.5 2H12V13H4.5C4.22386 13 4 13.2239 4 13.5C4 13.7761 4.22386 14 4.5 14H12C12.5523 14 13 13.5523 13 13V2C13 1.44772 12.5523 1 12 1H4.5ZM6.60355 4.89645C6.40829 4.70118 6.09171 4.70118 5.89645 4.89645C5.70118 5.09171 5.70118 5.40829 5.89645 5.60355L7.29289 7H0.5C0.223858 7 0 7.22386 0 7.5C0 7.77614 0.223858 8 0.5 8H7.29289L5.89645 9.39645C5.70118 9.59171 5.70118 9.90829 5.89645 10.1036C6.09171 10.2988 6.40829 10.2988 6.60355 10.1036L8.85355 7.85355C9.04882 7.65829 9.04882 7.34171 8.85355 7.14645L6.60355 4.89645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/envelope-closed.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 2C0.447715 2 0 2.44772 0 3V12C0 12.5523 0.447715 13 1 13H14C14.5523 13 15 12.5523 15 12V3C15 2.44772 14.5523 2 14 2H1ZM1 3L14 3V3.92494C13.9174 3.92486 13.8338 3.94751 13.7589 3.99505L7.5 7.96703L1.24112 3.99505C1.16621 3.94751 1.0826 3.92486 1 3.92494V3ZM1 4.90797V12H14V4.90797L7.74112 8.87995C7.59394 8.97335 7.40606 8.97335 7.25888 8.87995L1 4.90797Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/envelope-open.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.94721 0.164594C7.66569 0.0238299 7.33431 0.0238302 7.05279 0.164594L0.552786 3.41459C0.214002 3.58399 0 3.93025 0 4.30902V12C0 12.5523 0.447715 13 1 13H14C14.5523 13 15 12.5523 15 12V4.30902C15 3.93025 14.786 3.58399 14.4472 3.41459L7.94721 0.164594ZM13.5689 4.09349L7.5 1.05902L1.43105 4.09349L7.5 7.29136L13.5689 4.09349ZM1 4.88366V12H14V4.88366L7.70977 8.19813C7.57848 8.26731 7.42152 8.26731 7.29023 8.19813L1 4.88366Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/eraser.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.36052 0.72921C8.55578 0.533948 8.87236 0.533948 9.06763 0.72921L14.2708 5.93235C14.466 6.12761 14.466 6.4442 14.2708 6.63946L8.95513 11.9551L7.3466 13.5636C6.76081 14.1494 5.81106 14.1494 5.22528 13.5636L1.43635 9.7747C0.850563 9.18891 0.850563 8.23917 1.43635 7.65338L3.04488 6.04485L8.36052 0.72921ZM8.71407 1.78987L4.10554 6.3984L8.60157 10.8944L13.2101 6.28591L8.71407 1.78987ZM7.89447 11.6015L3.39843 7.10551L2.14346 8.36049C1.94819 8.55575 1.94819 8.87233 2.14346 9.06759L5.93238 12.8565C6.12765 13.0518 6.44423 13.0518 6.63949 12.8565L7.89447 11.6015Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/exclamation-triangle.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.4449 0.608765C8.0183 -0.107015 6.9817 -0.107015 6.55509 0.608766L0.161178 11.3368C-0.275824 12.07 0.252503 13 1.10608 13H13.8939C14.7475 13 15.2758 12.07 14.8388 11.3368L8.4449 0.608765ZM7.4141 1.12073C7.45288 1.05566 7.54712 1.05566 7.5859 1.12073L13.9798 11.8488C14.0196 11.9154 13.9715 12 13.8939 12H1.10608C1.02849 12 0.980454 11.9154 1.02018 11.8488L7.4141 1.12073ZM6.8269 4.48611C6.81221 4.10423 7.11783 3.78663 7.5 3.78663C7.88217 3.78663 8.18778 4.10423 8.1731 4.48612L8.01921 8.48701C8.00848 8.766 7.7792 8.98664 7.5 8.98664C7.2208 8.98664 6.99151 8.766 6.98078 8.48701L6.8269 4.48611ZM8.24989 10.476C8.24989 10.8902 7.9141 11.226 7.49989 11.226C7.08567 11.226 6.74989 10.8902 6.74989 10.476C6.74989 10.0618 7.08567 9.72599 7.49989 9.72599C7.9141 9.72599 8.24989 10.0618 8.24989 10.476Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/exit-full-screen.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.5 2C5.77614 2 6 2.22386 6 2.5V5.5C6 5.77614 5.77614 6 5.5 6H2.5C2.22386 6 2 5.77614 2 5.5C2 5.22386 2.22386 5 2.5 5H5V2.5C5 2.22386 5.22386 2 5.5 2ZM9.5 2C9.77614 2 10 2.22386 10 2.5V5H12.5C12.7761 5 13 5.22386 13 5.5C13 5.77614 12.7761 6 12.5 6H9.5C9.22386 6 9 5.77614 9 5.5V2.5C9 2.22386 9.22386 2 9.5 2ZM2 9.5C2 9.22386 2.22386 9 2.5 9H5.5C5.77614 9 6 9.22386 6 9.5V12.5C6 12.7761 5.77614 13 5.5 13C5.22386 13 5 12.7761 5 12.5V10H2.5C2.22386 10 2 9.77614 2 9.5ZM9 9.5C9 9.22386 9.22386 9 9.5 9H12.5C12.7761 9 13 9.22386 13 9.5C13 9.77614 12.7761 10 12.5 10H10V12.5C10 12.7761 9.77614 13 9.5 13C9.22386 13 9 12.7761 9 12.5V9.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/exit.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/external-link.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 2C2.44772 2 2 2.44772 2 3V12C2 12.5523 2.44772 13 3 13H12C12.5523 13 13 12.5523 13 12V8.5C13 8.22386 12.7761 8 12.5 8C12.2239 8 12 8.22386 12 8.5V12H3V3L6.5 3C6.77614 3 7 2.77614 7 2.5C7 2.22386 6.77614 2 6.5 2H3ZM12.8536 2.14645C12.9015 2.19439 12.9377 2.24964 12.9621 2.30861C12.9861 2.36669 12.9996 2.4303 13 2.497L13 2.5V2.50049V5.5C13 5.77614 12.7761 6 12.5 6C12.2239 6 12 5.77614 12 5.5V3.70711L6.85355 8.85355C6.65829 9.04882 6.34171 9.04882 6.14645 8.85355C5.95118 8.65829 5.95118 8.34171 6.14645 8.14645L11.2929 3H9.5C9.22386 3 9 2.77614 9 2.5C9 2.22386 9.22386 2 9.5 2H12.4999H12.5C12.5678 2 12.6324 2.01349 12.6914 2.03794C12.7504 2.06234 12.8056 2.09851 12.8536 2.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/eye-closed.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/eye-none.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3536 2.35355C13.5488 2.15829 13.5488 1.84171 13.3536 1.64645C13.1583 1.45118 12.8417 1.45118 12.6464 1.64645L10.6828 3.61012C9.70652 3.21671 8.63759 3 7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C0.902945 9.08812 2.02314 10.1861 3.36061 10.9323L1.64645 12.6464C1.45118 12.8417 1.45118 13.1583 1.64645 13.3536C1.84171 13.5488 2.15829 13.5488 2.35355 13.3536L4.31723 11.3899C5.29348 11.7833 6.36241 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C14.0971 5.9119 12.9769 4.81391 11.6394 4.06771L13.3536 2.35355ZM9.90428 4.38861C9.15332 4.1361 8.34759 4 7.5 4C4.80285 4 2.52952 5.37816 1.09622 7.50001C1.87284 8.6497 2.89609 9.58106 4.09974 10.1931L9.90428 4.38861ZM5.09572 10.6114L10.9003 4.80685C12.1039 5.41894 13.1272 6.35031 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11C6.65241 11 5.84668 10.8639 5.09572 10.6114Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/eye-open.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/face.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/figma-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/file-minus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.29289L12 4.70711V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.60355C13 4.40464 12.921 4.21388 12.7803 4.07322L9.85355 1.14645C9.75979 1.05268 9.63261 1 9.5 1H3.5ZM5.25 7C4.97386 7 4.75 7.22386 4.75 7.5C4.75 7.77614 4.97386 8 5.25 8H9.75C10.0261 8 10.25 7.77614 10.25 7.5C10.25 7.22386 10.0261 7 9.75 7H5.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/file-plus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.5 2C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V4.70711L9.29289 2H3.5ZM2 2.5C2 1.67157 2.67157 1 3.5 1H9.5C9.63261 1 9.75979 1.05268 9.85355 1.14645L12.7803 4.07322C12.921 4.21388 13 4.40464 13 4.60355V12.5C13 13.3284 12.3284 14 11.5 14H3.5C2.67157 14 2 13.3284 2 12.5V2.5ZM4.75 7.5C4.75 7.22386 4.97386 7 5.25 7H7V5.25C7 4.97386 7.22386 4.75 7.5 4.75C7.77614 4.75 8 4.97386 8 5.25V7H9.75C10.0261 7 10.25 7.22386 10.25 7.5C10.25 7.77614 10.0261 8 9.75 8H8V9.75C8 10.0261 7.77614 10.25 7.5 10.25C7.22386 10.25 7 10.0261 7 9.75V8H5.25C4.97386 8 4.75 7.77614 4.75 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/file-text.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.08579C9.21839 2 9.34557 2.05268 9.43934 2.14645L11.8536 4.56066C11.9473 4.65443 12 4.78161 12 4.91421V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.91421C13 4.51639 12.842 4.13486 12.5607 3.85355L10.1464 1.43934C9.86514 1.15804 9.48361 1 9.08579 1H3.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5H7.5C7.77614 5 8 4.77614 8 4.5C8 4.22386 7.77614 4 7.5 4H4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H10.5C10.7761 11 11 10.7761 11 10.5C11 10.2239 10.7761 10 10.5 10H4.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/file.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.5 2C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V6H8.5C8.22386 6 8 5.77614 8 5.5V2H3.5ZM9 2.70711L11.2929 5H9V2.70711ZM2 2.5C2 1.67157 2.67157 1 3.5 1H8.5C8.63261 1 8.75979 1.05268 8.85355 1.14645L12.8536 5.14645C12.9473 5.24021 13 5.36739 13 5.5V12.5C13 13.3284 12.3284 14 11.5 14H3.5C2.67157 14 2 13.3284 2 12.5V2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/font-bold.svg πŸ”—

@@ -0,0 +1,6 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    d="M5.10505 12C4.70805 12 4.4236 11.912 4.25171 11.736C4.0839 11.5559 4 11.2715 4 10.8827V4.11733C4 3.72033 4.08595 3.43588 4.25784 3.26398C4.43383 3.08799 4.71623 3 5.10505 3C6.42741 3 8.25591 3 9.02852 3C10.1373 3 11.0539 3.98153 11.0539 5.1846C11.0539 6.08501 10.6037 6.81855 9.70327 7.23602C10.8657 7.44851 11.5176 8.62787 11.5176 9.48128C11.5176 10.5125 10.9902 12 9.27734 12C8.77742 12 6.42626 12 5.10505 12ZM8.37891 8.00341H5.8V10.631H8.37891C8.9 10.631 9.6296 10.1211 9.6296 9.29877C9.6296 8.47643 8.9 8.00341 8.37891 8.00341ZM5.8 4.36903V6.69577H8.17969C8.53906 6.69577 9.27734 6.35939 9.27734 5.50002C9.27734 4.64064 8.48047 4.36903 8.17969 4.36903H5.8Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/font-family.svg πŸ”—

@@ -0,0 +1,6 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    d="M2.5 4.5C2.5 3.09886 3.59886 2 5 2H12.499C12.7752 2 13 2.22386 13 2.5C13 2.77614 12.7761 3 12.5 3H8.69244L8.40509 3.85458C8.18869 4.49752 7.89401 5.37197 7.58091 6.29794C7.50259 6.52956 7.42308 6.76453 7.34332 7H8.5C8.77614 7 9 7.22386 9 7.5C9 7.77614 8.77614 8 8.5 8H7.00407C6.56724 9.28543 6.16435 10.4613 5.95799 11.0386C5.63627 11.9386 5.20712 12.4857 4.66741 12.7778C4.16335 13.0507 3.64154 13.0503 3.28378 13.05L3.25 13.05C2.94624 13.05 2.7 12.8037 2.7 12.5C2.7 12.1962 2.94624 11.95 3.25 11.95C3.64182 11.95 3.9035 11.9405 4.14374 11.8105C4.36443 11.691 4.65532 11.4148 4.92217 10.6683C5.10695 10.1514 5.45375 9.14134 5.8422 8H4.5C4.22386 8 4 7.77614 4 7.5C4 7.22386 4.22386 7 4.5 7H6.18187C6.30127 6.64785 6.42132 6.29323 6.53887 5.94559C6.85175 5.02025 7.14627 4.14631 7.36256 3.50368L7.53192 3H5C4.15114 3 3.5 3.65114 3.5 4.5C3.5 4.77614 3.27614 5 3 5C2.72386 5 2.5 4.77614 2.5 4.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/font-italic.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.67494 3.50017C5.67494 3.25164 5.87641 3.05017 6.12494 3.05017H10.6249C10.8735 3.05017 11.0749 3.25164 11.0749 3.50017C11.0749 3.7487 10.8735 3.95017 10.6249 3.95017H9.00587L7.2309 11.05H8.87493C9.12345 11.05 9.32493 11.2515 9.32493 11.5C9.32493 11.7486 9.12345 11.95 8.87493 11.95H4.37493C4.1264 11.95 3.92493 11.7486 3.92493 11.5C3.92493 11.2515 4.1264 11.05 4.37493 11.05H5.99397L7.76894 3.95017H6.12494C5.87641 3.95017 5.67494 3.7487 5.67494 3.50017Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/font-roman.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.79993 3.50017C4.79993 3.25164 5.0014 3.05017 5.24993 3.05017H9.74993C9.99845 3.05017 10.1999 3.25164 10.1999 3.50017C10.1999 3.7487 9.99845 3.95017 9.74993 3.95017H8.09993V11.05H9.74994C9.99847 11.05 10.1999 11.2515 10.1999 11.5C10.1999 11.7486 9.99847 11.95 9.74994 11.95H5.24994C5.00141 11.95 4.79994 11.7486 4.79994 11.5C4.79994 11.2515 5.00141 11.05 5.24994 11.05H6.89993V3.95017H5.24993C5.0014 3.95017 4.79993 3.7487 4.79993 3.50017Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/font-size.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/font-style.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/frame.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11 1.5C11 1.22386 10.7761 1 10.5 1C10.2239 1 10 1.22386 10 1.5V4H5V1.5C5 1.22386 4.77614 1 4.5 1C4.22386 1 4 1.22386 4 1.5V4H1.5C1.22386 4 1 4.22386 1 4.5C1 4.77614 1.22386 5 1.5 5H4V10H1.5C1.22386 10 1 10.2239 1 10.5C1 10.7761 1.22386 11 1.5 11H4V13.5C4 13.7761 4.22386 14 4.5 14C4.77614 14 5 13.7761 5 13.5V11H10V13.5C10 13.7761 10.2239 14 10.5 14C10.7761 14 11 13.7761 11 13.5V11H13.5C13.7761 11 14 10.7761 14 10.5C14 10.2239 13.7761 10 13.5 10H11V5H13.5C13.7761 5 14 4.77614 14 4.5C14 4.22386 13.7761 4 13.5 4H11V1.5ZM10 10V5H5V10H10Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/framer-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.3825 1.29567C3.46241 1.11432 3.64188 0.997284 3.84005 0.997284H11.5C11.7761 0.997284 12 1.22114 12 1.49728V5.5C12 5.77614 11.7761 6 11.5 6H8.63521L11.5288 9.16247C11.6626 9.3087 11.6974 9.52015 11.6175 9.70154C11.5376 9.88293 11.3582 10 11.16 10H8V13.5C8 13.7022 7.87818 13.8845 7.69134 13.9619C7.5045 14.0393 7.28945 13.9966 7.14645 13.8536L3.14645 9.85355C3.05268 9.75979 3 9.63261 3 9.5V5.5C3 5.22386 3.22386 5 3.5 5H6.36531L3.47105 1.83468C3.33732 1.68844 3.30259 1.47701 3.3825 1.29567ZM7.72032 5L4.97474 1.99728H11V5H7.72032ZM7.27978 6H4V9H7.5H10.0247L7.27978 6ZM4.70711 10L7 12.2929V10H4.70711Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/gear.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/github-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/globe.svg πŸ”—

@@ -0,0 +1,26 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49996 1.80002C4.35194 1.80002 1.79996 4.352 1.79996 7.50002C1.79996 10.648 4.35194 13.2 7.49996 13.2C10.648 13.2 13.2 10.648 13.2 7.50002C13.2 4.352 10.648 1.80002 7.49996 1.80002ZM0.899963 7.50002C0.899963 3.85494 3.85488 0.900024 7.49996 0.900024C11.145 0.900024 14.1 3.85494 14.1 7.50002C14.1 11.1451 11.145 14.1 7.49996 14.1C3.85488 14.1 0.899963 11.1451 0.899963 7.50002Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.4999 7.89998H1.49994V7.09998H13.4999V7.89998Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.09991 13.5V1.5H7.89991V13.5H7.09991zM10.375 7.49998C10.375 5.32724 9.59364 3.17778 8.06183 1.75656L8.53793 1.24341C10.2396 2.82218 11.075 5.17273 11.075 7.49998 11.075 9.82724 10.2396 12.1778 8.53793 13.7566L8.06183 13.2434C9.59364 11.8222 10.375 9.67273 10.375 7.49998zM3.99969 7.5C3.99969 5.17611 4.80786 2.82678 6.45768 1.24719L6.94177 1.75281C5.4582 3.17323 4.69969 5.32389 4.69969 7.5 4.6997 9.67611 5.45822 11.8268 6.94179 13.2472L6.45769 13.7528C4.80788 12.1732 3.9997 9.8239 3.99969 7.5z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49996 3.95801C9.66928 3.95801 11.8753 4.35915 13.3706 5.19448 13.5394 5.28875 13.5998 5.50197 13.5055 5.67073 13.4113 5.83948 13.198 5.89987 13.0293 5.8056 11.6794 5.05155 9.60799 4.65801 7.49996 4.65801 5.39192 4.65801 3.32052 5.05155 1.97064 5.8056 1.80188 5.89987 1.58866 5.83948 1.49439 5.67073 1.40013 5.50197 1.46051 5.28875 1.62927 5.19448 3.12466 4.35915 5.33063 3.95801 7.49996 3.95801zM7.49996 10.85C9.66928 10.85 11.8753 10.4488 13.3706 9.6135 13.5394 9.51924 13.5998 9.30601 13.5055 9.13726 13.4113 8.9685 13.198 8.90812 13.0293 9.00238 11.6794 9.75643 9.60799 10.15 7.49996 10.15 5.39192 10.15 3.32052 9.75643 1.97064 9.00239 1.80188 8.90812 1.58866 8.9685 1.49439 9.13726 1.40013 9.30601 1.46051 9.51924 1.62927 9.6135 3.12466 10.4488 5.33063 10.85 7.49996 10.85z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/grid.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.5 2H8V7H13V2.5C13 2.22386 12.7761 2 12.5 2ZM13 8H8V13H12.5C12.7761 13 13 12.7761 13 12.5V8ZM7 7V2H2.5C2.22386 2 2 2.22386 2 2.5V7H7ZM2 8V12.5C2 12.7761 2.22386 13 2.5 13H7V8H2ZM2.5 1C1.67157 1 1 1.67157 1 2.5V12.5C1 13.3284 1.67157 14 2.5 14H12.5C13.3284 14 14 13.3284 14 12.5V2.5C14 1.67157 13.3284 1 12.5 1H2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/group.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/half-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.876892C3.84222 0.876892 0.877075 3.84204 0.877075 7.49972C0.877075 11.1574 3.84222 14.1226 7.49991 14.1226C11.1576 14.1226 14.1227 11.1574 14.1227 7.49972C14.1227 3.84204 11.1576 0.876892 7.49991 0.876892ZM7.00003 1.84861C4.10114 2.1017 1.82707 4.53515 1.82707 7.49972C1.82707 10.4643 4.10114 12.8977 7.00003 13.1508V1.84861ZM8.00003 13.1508C10.8988 12.8976 13.1727 10.4642 13.1727 7.49972C13.1727 4.53524 10.8988 2.10185 8.00003 1.84864V13.1508Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/half-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.876892C3.84222 0.876892 0.877075 3.84204 0.877075 7.49972C0.877075 11.1574 3.84222 14.1226 7.49991 14.1226C11.1576 14.1226 14.1227 11.1574 14.1227 7.49972C14.1227 3.84204 11.1576 0.876892 7.49991 0.876892ZM7.49988 1.82689C4.36688 1.8269 1.82707 4.36672 1.82707 7.49972C1.82707 10.6327 4.36688 13.1725 7.49988 13.1726V1.82689Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/hamburger-menu.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5 8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761 13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/hand.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/heading.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.75432 2.0502C8.50579 2.0502 8.30432 2.25167 8.30432 2.5002C8.30432 2.74873 8.50579 2.9502 8.75432 2.9502H9.94997V7.05004H5.04997V2.9502H6.25432C6.50285 2.9502 6.70432 2.74873 6.70432 2.5002C6.70432 2.25167 6.50285 2.0502 6.25432 2.0502H2.75432C2.50579 2.0502 2.30432 2.25167 2.30432 2.5002C2.30432 2.74873 2.50579 2.9502 2.75432 2.9502H3.94997V12.0502H2.75432C2.50579 12.0502 2.30432 12.2517 2.30432 12.5002C2.30432 12.7487 2.50579 12.9502 2.75432 12.9502H6.25432C6.50285 12.9502 6.70432 12.7487 6.70432 12.5002C6.70432 12.2517 6.50285 12.0502 6.25432 12.0502H5.04997V7.95004H9.94997V12.0502H8.75432C8.50579 12.0502 8.30432 12.2517 8.30432 12.5002C8.30432 12.7487 8.50579 12.9502 8.75432 12.9502H12.2543C12.5028 12.9502 12.7043 12.7487 12.7043 12.5002C12.7043 12.2517 12.5028 12.0502 12.2543 12.0502H11.05V2.9502H12.2543C12.5028 2.9502 12.7043 2.74873 12.7043 2.5002C12.7043 2.25167 12.5028 2.0502 12.2543 2.0502H8.75432Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/heart-filled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.35248 4.90532C1.35248 2.94498 2.936 1.35248 4.89346 1.35248C6.25769 1.35248 6.86058 1.92336 7.50002 2.93545C8.13946 1.92336 8.74235 1.35248 10.1066 1.35248C12.064 1.35248 13.6476 2.94498 13.6476 4.90532C13.6476 6.74041 12.6013 8.50508 11.4008 9.96927C10.2636 11.3562 8.92194 12.5508 8.00601 13.3664C7.94645 13.4194 7.88869 13.4709 7.83291 13.5206C7.64324 13.6899 7.3568 13.6899 7.16713 13.5206C7.11135 13.4709 7.05359 13.4194 6.99403 13.3664C6.0781 12.5508 4.73641 11.3562 3.59926 9.96927C2.39872 8.50508 1.35248 6.74041 1.35248 4.90532Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/heart.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/height.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.1813 1.68179C7.35704 1.50605 7.64196 1.50605 7.8177 1.68179L10.3177 4.18179C10.4934 4.35753 10.4934 4.64245 10.3177 4.81819C10.142 4.99392 9.85704 4.99392 9.6813 4.81819L7.9495 3.08638L7.9495 11.9136L9.6813 10.1818C9.85704 10.0061 10.142 10.0061 10.3177 10.1818C10.4934 10.3575 10.4934 10.6424 10.3177 10.8182L7.8177 13.3182C7.73331 13.4026 7.61885 13.45 7.4995 13.45C7.38015 13.45 7.26569 13.4026 7.1813 13.3182L4.6813 10.8182C4.50557 10.6424 4.50557 10.3575 4.6813 10.1818C4.85704 10.0061 5.14196 10.0061 5.3177 10.1818L7.0495 11.9136L7.0495 3.08638L5.3177 4.81819C5.14196 4.99392 4.85704 4.99392 4.6813 4.81819C4.50557 4.64245 4.50557 4.35753 4.6813 4.18179L7.1813 1.68179Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/hobby-knife.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.3536 13.3536C12.1583 13.5488 11.8417 13.5488 11.6465 13.3536L6.39645 8.10355C6.36478 8.07188 6.33824 8.03702 6.31685 8H5.00002C4.78719 8 4.59769 7.86528 4.52777 7.66426L2.12777 0.764277C2.05268 0.548387 2.13355 0.309061 2.3242 0.182972C2.51486 0.0568819 2.76674 0.0761337 2.93602 0.229734L8.336 5.12972C8.44044 5.22449 8.50001 5.35897 8.50001 5.5V5.81684C8.53702 5.83824 8.57189 5.86478 8.60356 5.89645L13.8536 11.1464C14.0488 11.3417 14.0488 11.6583 13.8536 11.8536L12.3536 13.3536ZM8.25 6.95711L7.45711 7.75L12 12.2929L12.7929 11.5L8.25 6.95711ZM3.71669 2.28845L5.35549 7H6.2929L7.50001 5.79289V5.72146L3.71669 2.28845Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/home.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.07926 0.222253C7.31275 -0.007434 7.6873 -0.007434 7.92079 0.222253L14.6708 6.86227C14.907 7.09465 14.9101 7.47453 14.6778 7.71076C14.4454 7.947 14.0655 7.95012 13.8293 7.71773L13 6.90201V12.5C13 12.7761 12.7762 13 12.5 13H2.50002C2.22388 13 2.00002 12.7761 2.00002 12.5V6.90201L1.17079 7.71773C0.934558 7.95012 0.554672 7.947 0.32229 7.71076C0.0899079 7.47453 0.0930283 7.09465 0.32926 6.86227L7.07926 0.222253ZM7.50002 1.49163L12 5.91831V12H10V8.49999C10 8.22385 9.77617 7.99999 9.50002 7.99999H6.50002C6.22388 7.99999 6.00002 8.22385 6.00002 8.49999V12H3.00002V5.91831L7.50002 1.49163ZM7.00002 12H9.00002V8.99999H7.00002V12Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/id-card.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14 11.0001V4.00006L1 4.00006L1 11.0001H14ZM15 4.00006V11.0001C15 11.5523 14.5523 12.0001 14 12.0001H1C0.447715 12.0001 0 11.5523 0 11.0001V4.00006C0 3.44778 0.447715 3.00006 1 3.00006H14C14.5523 3.00006 15 3.44778 15 4.00006ZM2 5.25C2 5.11193 2.11193 5 2.25 5H5.75C5.88807 5 6 5.11193 6 5.25V9.75C6 9.88807 5.88807 10 5.75 10H2.25C2.11193 10 2 9.88807 2 9.75V5.25ZM7.5 7C7.22386 7 7 7.22386 7 7.5C7 7.77614 7.22386 8 7.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H7.5ZM7 9.5C7 9.22386 7.22386 9 7.5 9H12.5C12.7761 9 13 9.22386 13 9.5C13 9.77614 12.7761 10 12.5 10H7.5C7.22386 10 7 9.77614 7 9.5ZM7.5 5C7.22386 5 7 5.22386 7 5.5C7 5.77614 7.22386 6 7.5 6H11.5C11.7761 6 12 5.77614 12 5.5C12 5.22386 11.7761 5 11.5 5H7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/image.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.5 1H12.5C13.3284 1 14 1.67157 14 2.5V12.5C14 13.3284 13.3284 14 12.5 14H2.5C1.67157 14 1 13.3284 1 12.5V2.5C1 1.67157 1.67157 1 2.5 1ZM2.5 2C2.22386 2 2 2.22386 2 2.5V8.3636L3.6818 6.6818C3.76809 6.59551 3.88572 6.54797 4.00774 6.55007C4.12975 6.55216 4.24568 6.60372 4.32895 6.69293L7.87355 10.4901L10.6818 7.6818C10.8575 7.50607 11.1425 7.50607 11.3182 7.6818L13 9.3636V2.5C13 2.22386 12.7761 2 12.5 2H2.5ZM2 12.5V9.6364L3.98887 7.64753L7.5311 11.4421L8.94113 13H2.5C2.22386 13 2 12.7761 2 12.5ZM12.5 13H10.155L8.48336 11.153L11 8.6364L13 10.6364V12.5C13 12.7761 12.7761 13 12.5 13ZM6.64922 5.5C6.64922 5.03013 7.03013 4.64922 7.5 4.64922C7.96987 4.64922 8.35078 5.03013 8.35078 5.5C8.35078 5.96987 7.96987 6.35078 7.5 6.35078C7.03013 6.35078 6.64922 5.96987 6.64922 5.5ZM7.5 3.74922C6.53307 3.74922 5.74922 4.53307 5.74922 5.5C5.74922 6.46693 6.53307 7.25078 7.5 7.25078C8.46693 7.25078 9.25078 6.46693 9.25078 5.5C9.25078 4.53307 8.46693 3.74922 7.5 3.74922Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/info-circled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.876892C3.84222 0.876892 0.877075 3.84204 0.877075 7.49972C0.877075 11.1574 3.84222 14.1226 7.49991 14.1226C11.1576 14.1226 14.1227 11.1574 14.1227 7.49972C14.1227 3.84204 11.1576 0.876892 7.49991 0.876892ZM1.82707 7.49972C1.82707 4.36671 4.36689 1.82689 7.49991 1.82689C10.6329 1.82689 13.1727 4.36671 13.1727 7.49972C13.1727 10.6327 10.6329 13.1726 7.49991 13.1726C4.36689 13.1726 1.82707 10.6327 1.82707 7.49972ZM8.24992 4.49999C8.24992 4.9142 7.91413 5.24999 7.49992 5.24999C7.08571 5.24999 6.74992 4.9142 6.74992 4.49999C6.74992 4.08577 7.08571 3.74999 7.49992 3.74999C7.91413 3.74999 8.24992 4.08577 8.24992 4.49999ZM6.00003 5.99999H6.50003H7.50003C7.77618 5.99999 8.00003 6.22384 8.00003 6.49999V9.99999H8.50003H9.00003V11H8.50003H7.50003H6.50003H6.00003V9.99999H6.50003H7.00003V6.99999H6.50003H6.00003V5.99999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/inner-shadow.svg πŸ”—

@@ -0,0 +1,78 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49988C0.877075 3.84219 3.84222 0.877045 7.49991 0.877045C11.1576 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49988ZM7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 4.36686 10.6329 1.82704 7.49991 1.82704Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.1619 3.85182C8.35817 4.88918 4.88936 8.358 3.85199 12.1617L3.3696 12.0301C4.45356 8.05564 8.05581 4.45339 12.0303 3.36943L12.1619 3.85182Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".1"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.8807 3.42707C8.03441 4.50542 4.50561 8.03422 3.42726 11.8805L2.94582 11.7456C4.07129 7.73121 7.7314 4.0711 11.7458 2.94563L11.8807 3.42707Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".15"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.5201 3.02556C7.69092 4.16199 4.16779 7.68323 3.02805 11.512L2.54883 11.3694C3.73676 7.37869 7.38659 3.73076 11.3778 2.54623L11.5201 3.02556Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.0468 2.66169C7.31117 3.87664 3.87918 7.3079 2.66298 11.0434L2.18754 10.8886C3.45324 7.00109 7.00445 3.45062 10.8921 2.18621L11.0468 2.66169Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".25"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.5201 2.32365C6.92091 3.61447 3.62391 6.90876 2.32845 10.5073L1.858 10.338C3.20398 6.59909 6.61155 3.19424 10.3513 1.85301L10.5201 2.32365Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".3"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.90222 2.03122C6.50003 3.39465 3.39968 6.49367 2.03399 9.89551L1.56998 9.70924C2.98651 6.18076 6.18728 2.98133 9.71622 1.5671L9.90222 2.03122Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.20727 1.78873C6.06136 3.20349 3.21103 6.05203 1.79331 9.19738L1.33747 8.99192C2.80536 5.73528 5.74485 2.7976 9.0022 1.33272L9.20727 1.78873Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".4"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.40713 1.62085C5.59323 3.05117 3.05794 5.58509 1.62544 8.39847L1.17987 8.1716C2.66036 5.26397 5.27232 2.6534 8.18057 1.17513L8.40713 1.62085Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".45"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.46207 1.56747C5.08689 2.94695 2.95362 5.07912 1.57249 7.45379L1.14028 7.20241C2.56503 4.75273 4.7607 2.55818 7.21096 1.1351L7.46207 1.56747Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.30407 1.70487C4.51964 2.91063 2.90983 4.52061 1.7043 6.30513L1.28998 6.02524C2.5313 4.18773 4.18673 2.53214 6.02413 1.29059L6.30407 1.70487Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/input.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/justify-center.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.94998 5.99994L11 5.99994L11 8.99994L7.94998 8.99994L7.94998 5.99994ZM7.94998 4.99994L7.94998 1.49913C7.94998 1.25061 7.74851 1.04913 7.49998 1.04913C7.25145 1.04913 7.04998 1.2506 7.04998 1.49913L7.04998 4.99994L3.75 4.99994C3.33579 4.99994 3 5.33572 3 5.74994L3 9.24994C3 9.66415 3.33579 9.99994 3.75 9.99994L7.04998 9.99994L7.04998 13.4991C7.04998 13.7477 7.25145 13.9491 7.49998 13.9491C7.7485 13.9491 7.94998 13.7477 7.94998 13.4991L7.94998 9.99994L11.25 9.99994C11.6642 9.99994 12 9.66415 12 9.24994L12 5.74994C12 5.33573 11.6642 4.99994 11.25 4.99994L7.94998 4.99994ZM7.04998 8.99994L4 8.99994L4 5.99994L7.04998 5.99994L7.04998 8.99994Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/justify-end.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.95 1.49953C13.95 1.251 13.7485 1.04953 13.5 1.04953C13.2514 1.04953 13.05 1.251 13.05 1.49953L13.05 13.4995C13.05 13.7481 13.2514 13.9495 13.5 13.9495C13.7485 13.9495 13.95 13.7481 13.95 13.4995L13.95 1.49953ZM3.99997 5.99997L11 5.99997L11 8.99997L3.99997 8.99997L3.99997 5.99997ZM11.25 4.99997C11.6642 4.99997 12 5.33576 12 5.74997L12 9.24997C12 9.66418 11.6642 9.99997 11.25 9.99997L3.74997 9.99997C3.33576 9.99997 2.99997 9.66418 2.99997 9.24997L2.99997 5.74997C2.99998 5.33576 3.33576 4.99997 3.74998 4.99997L11.25 4.99997Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/justify-start.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.05005 13.5005C1.05005 13.749 1.25152 13.9505 1.50005 13.9505C1.74858 13.9505 1.95005 13.749 1.95005 13.5005L1.95005 1.50047C1.95005 1.25194 1.74858 1.05047 1.50005 1.05047C1.25152 1.05047 1.05005 1.25194 1.05005 1.50047L1.05005 13.5005ZM11 9.00003L4.00002 9.00003L4.00002 6.00003L11 6.00003L11 9.00003ZM3.75002 10C3.33581 10 3.00002 9.66424 3.00002 9.25003L3.00002 5.75003C3.00002 5.33582 3.33581 5.00003 3.75002 5.00003L11.25 5.00003C11.6642 5.00003 12 5.33582 12 5.75003L12 9.25003C12 9.66424 11.6642 10 11.25 10L3.75002 10Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/justify-stretch.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.5 1.04956C13.7485 1.04956 13.95 1.25103 13.95 1.49956L13.95 13.4996C13.95 13.7481 13.7485 13.9496 13.5 13.9496C13.2514 13.9496 13.05 13.7481 13.05 13.4996L13.05 1.49956C13.05 1.25103 13.2514 1.04956 13.5 1.04956ZM1.49995 1.04966C1.74848 1.04966 1.94995 1.25113 1.94995 1.49966L1.94995 13.4997C1.94995 13.7482 1.74848 13.9497 1.49995 13.9497C1.25142 13.9497 1.04995 13.7482 1.04995 13.4997L1.04995 1.49966C1.04995 1.25113 1.25142 1.04966 1.49995 1.04966ZM3.99997 6L11 6L11 9L3.99997 9L3.99997 6ZM11.25 5C11.6642 5 12 5.33579 12 5.75L12 9.25C12 9.66421 11.6642 10 11.25 10L3.74997 10C3.33576 10 2.99997 9.66421 2.99997 9.25L2.99997 5.75C2.99998 5.33579 3.33576 5 3.74998 5L11.25 5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/keyboard.svg πŸ”—

@@ -0,0 +1,7 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect x=".5" y="3.5" width="14" height="8" rx="1" stroke="currentColor" />
+  <path
+    fill="currentColor"
+    d="M2 5H3V6H2zM4 5H5V6H4zM6 5H7V6H6zM8 5H9V6H8zM10 5H11V6H10zM12 5H13V6H12zM11 7H12V8H11zM12 9H13V10H12zM9 7H10V8H9zM7 7H8V8H7zM5 7H6V8H5zM3 7H4V8H3zM2 9H3V10H2zM4 9H11V10H4z"
+  />
+</svg>

assets/icons/radix/lap-timer.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.49998 0.5C5.49998 0.223858 5.72383 0 5.99998 0H7.49998H8.99998C9.27612 0 9.49998 0.223858 9.49998 0.5C9.49998 0.776142 9.27612 1 8.99998 1H7.99998V2.11922C9.09832 2.20409 10.119 2.56622 10.992 3.13572C11.0116 3.10851 11.0336 3.08252 11.058 3.05806L12.058 2.05806C12.3021 1.81398 12.6978 1.81398 12.9419 2.05806C13.186 2.30214 13.186 2.69786 12.9419 2.94194L11.967 3.91682C13.1595 5.07925 13.9 6.70314 13.9 8.49998C13.9 12.0346 11.0346 14.9 7.49998 14.9C3.96535 14.9 1.09998 12.0346 1.09998 8.49998C1.09998 5.13361 3.69904 2.3743 6.99998 2.11922V1H5.99998C5.72383 1 5.49998 0.776142 5.49998 0.5ZM2.09998 8.49998C2.09998 5.51764 4.51764 3.09998 7.49998 3.09998C10.4823 3.09998 12.9 5.51764 12.9 8.49998C12.9 11.4823 10.4823 13.9 7.49998 13.9C4.51764 13.9 2.09998 11.4823 2.09998 8.49998ZM7.49998 8.49998V4.09998C5.06992 4.09998 3.09998 6.06992 3.09998 8.49998C3.09998 10.93 5.06992 12.9 7.49998 12.9C8.715 12.9 9.815 12.4075 10.6112 11.6112L7.49998 8.49998Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/laptop.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 4.25C2 4.11193 2.11193 4 2.25 4H12.75C12.8881 4 13 4.11193 13 4.25V11.5H2V4.25ZM2.25 3C1.55964 3 1 3.55964 1 4.25V12H0V12.5C0 12.7761 0.223858 13 0.5 13H14.5C14.7761 13 15 12.7761 15 12.5V12H14V4.25C14 3.55964 13.4404 3 12.75 3H2.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/layers.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/layout.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9 2H6V13H9V2ZM10 2V13H12.5C12.7761 13 13 12.7761 13 12.5V2.5C13 2.22386 12.7761 2 12.5 2H10ZM2.5 2H5V13H2.5C2.22386 13 2 12.7761 2 12.5V2.5C2 2.22386 2.22386 2 2.5 2ZM2.5 1C1.67157 1 1 1.67157 1 2.5V12.5C1 13.3284 1.67157 14 2.5 14H12.5C13.3284 14 14 13.3284 14 12.5V2.5C14 1.67157 13.3284 1 12.5 1H2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/letter-case-uppercase.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.6255 2.75C3.83478 2.75 4.02192 2.88034 4.09448 3.07664L7.16985 11.3962C7.2656 11.6552 7.13324 11.9428 6.87423 12.0386C6.61522 12.1343 6.32763 12.002 6.23188 11.7429L5.22387 9.01603H2.02712L1.01911 11.7429C0.923362 12.002 0.635774 12.1343 0.376762 12.0386C0.117749 11.9428 -0.0146052 11.6552 0.0811401 11.3962L3.15651 3.07664C3.22908 2.88034 3.41621 2.75 3.6255 2.75ZM3.6255 4.69207L4.90966 8.16603H2.34133L3.6255 4.69207ZM11.3719 2.75C11.5811 2.75 11.7683 2.88034 11.8408 3.07664L14.9162 11.3962C15.012 11.6552 14.8796 11.9428 14.6206 12.0386C14.3616 12.1343 14.074 12.002 13.9782 11.7429L12.9702 9.01603H9.77348L8.76547 11.7429C8.66972 12.002 8.38213 12.1343 8.12312 12.0386C7.86411 11.9428 7.73175 11.6552 7.8275 11.3962L10.9029 3.07664C10.9754 2.88034 11.1626 2.75 11.3719 2.75ZM11.3719 4.69207L12.656 8.16603H10.0877L11.3719 4.69207Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/lightning-bolt.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.69667 0.0403541C8.90859 0.131038 9.03106 0.354857 8.99316 0.582235L8.0902 6.00001H12.5C12.6893 6.00001 12.8625 6.10701 12.9472 6.27641C13.0319 6.4458 13.0136 6.6485 12.8999 6.80001L6.89997 14.8C6.76167 14.9844 6.51521 15.0503 6.30328 14.9597C6.09135 14.869 5.96888 14.6452 6.00678 14.4178L6.90974 9H2.49999C2.31061 9 2.13748 8.893 2.05278 8.72361C1.96809 8.55422 1.98636 8.35151 2.09999 8.2L8.09997 0.200038C8.23828 0.0156255 8.48474 -0.0503301 8.69667 0.0403541ZM3.49999 8.00001H7.49997C7.64695 8.00001 7.78648 8.06467 7.88148 8.17682C7.97648 8.28896 8.01733 8.43723 7.99317 8.5822L7.33027 12.5596L11.5 7.00001H7.49997C7.353 7.00001 7.21347 6.93534 7.11846 6.8232C7.02346 6.71105 6.98261 6.56279 7.00678 6.41781L7.66968 2.44042L3.49999 8.00001Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/line-height.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/linkedin-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 1C1.44772 1 1 1.44772 1 2V13C1 13.5523 1.44772 14 2 14H13C13.5523 14 14 13.5523 14 13V2C14 1.44772 13.5523 1 13 1H2ZM3.05 6H4.95V12H3.05V6ZM5.075 4.005C5.075 4.59871 4.59371 5.08 4 5.08C3.4063 5.08 2.925 4.59871 2.925 4.005C2.925 3.41129 3.4063 2.93 4 2.93C4.59371 2.93 5.075 3.41129 5.075 4.005ZM12 8.35713C12 6.55208 10.8334 5.85033 9.67449 5.85033C9.29502 5.83163 8.91721 5.91119 8.57874 6.08107C8.32172 6.21007 8.05265 6.50523 7.84516 7.01853H7.79179V6.00044H6V12.0047H7.90616V8.8112C7.8786 8.48413 7.98327 8.06142 8.19741 7.80987C8.41156 7.55832 8.71789 7.49825 8.95015 7.46774H9.02258C9.62874 7.46774 10.0786 7.84301 10.0786 8.78868V12.0047H11.9847L12 8.35713Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/list-bullet.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.5 5.25C1.91421 5.25 2.25 4.91421 2.25 4.5C2.25 4.08579 1.91421 3.75 1.5 3.75C1.08579 3.75 0.75 4.08579 0.75 4.5C0.75 4.91421 1.08579 5.25 1.5 5.25ZM4 4.5C4 4.22386 4.22386 4 4.5 4H13.5C13.7761 4 14 4.22386 14 4.5C14 4.77614 13.7761 5 13.5 5H4.5C4.22386 5 4 4.77614 4 4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H13.5C13.7761 8 14 7.77614 14 7.5C14 7.22386 13.7761 7 13.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H13.5C13.7761 11 14 10.7761 14 10.5C14 10.2239 13.7761 10 13.5 10H4.5ZM2.25 7.5C2.25 7.91421 1.91421 8.25 1.5 8.25C1.08579 8.25 0.75 7.91421 0.75 7.5C0.75 7.08579 1.08579 6.75 1.5 6.75C1.91421 6.75 2.25 7.08579 2.25 7.5ZM1.5 11.25C1.91421 11.25 2.25 10.9142 2.25 10.5C2.25 10.0858 1.91421 9.75 1.5 9.75C1.08579 9.75 0.75 10.0858 0.75 10.5C0.75 10.9142 1.08579 11.25 1.5 11.25Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/lock-closed.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5 4.63601C5 3.76031 5.24219 3.1054 5.64323 2.67357C6.03934 2.24705 6.64582 1.9783 7.5014 1.9783C8.35745 1.9783 8.96306 2.24652 9.35823 2.67208C9.75838 3.10299 10 3.75708 10 4.63325V5.99999H5V4.63601ZM4 5.99999V4.63601C4 3.58148 4.29339 2.65754 4.91049 1.99307C5.53252 1.32329 6.42675 0.978302 7.5014 0.978302C8.57583 0.978302 9.46952 1.32233 10.091 1.99162C10.7076 2.65557 11 3.57896 11 4.63325V5.99999H12C12.5523 5.99999 13 6.44771 13 6.99999V13C13 13.5523 12.5523 14 12 14H3C2.44772 14 2 13.5523 2 13V6.99999C2 6.44771 2.44772 5.99999 3 5.99999H4ZM3 6.99999H12V13H3V6.99999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/lock-open-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.4986 0C6.3257 0 5.36107 0.38943 4.73753 1.19361C4.23745 1.83856 4 2.68242 4 3.63325H5C5 2.84313 5.19691 2.23312 5.5278 1.80636C5.91615 1.30552 6.55152 1 7.4986 1C8.35683 1 8.96336 1.26502 9.35846 1.68623C9.75793 2.11211 10 2.76044 10 3.63601V6H3C2.44772 6 2 6.44772 2 7V13C2 13.5523 2.44772 14 3 14H12C12.5523 14 13 13.5523 13 13V7C13 6.44771 12.5523 6 12 6H11V3.63601C11 2.58135 10.7065 1.66167 10.0878 1.0021C9.46477 0.337871 8.57061 0 7.4986 0ZM3 7H12V13H3V7Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/lock-open-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9 3.63601C9 2.76044 9.24207 2.11211 9.64154 1.68623C10.0366 1.26502 10.6432 1 11.5014 1C12.4485 1 13.0839 1.30552 13.4722 1.80636C13.8031 2.23312 14 2.84313 14 3.63325H15C15 2.68242 14.7626 1.83856 14.2625 1.19361C13.6389 0.38943 12.6743 0 11.5014 0C10.4294 0 9.53523 0.337871 8.91218 1.0021C8.29351 1.66167 8 2.58135 8 3.63601V6H1C0.447715 6 0 6.44772 0 7V13C0 13.5523 0.447715 14 1 14H10C10.5523 14 11 13.5523 11 13V7C11 6.44772 10.5523 6 10 6H9V3.63601ZM1 7H10V13H1V7Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/loop.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.35355 1.85355C3.54882 1.65829 3.54882 1.34171 3.35355 1.14645C3.15829 0.951184 2.84171 0.951184 2.64645 1.14645L0.646447 3.14645C0.451184 3.34171 0.451184 3.65829 0.646447 3.85355L2.64645 5.85355C2.84171 6.04882 3.15829 6.04882 3.35355 5.85355C3.54882 5.65829 3.54882 5.34171 3.35355 5.14645L2.20711 4H9.5C11.433 4 13 5.567 13 7.5C13 7.77614 13.2239 8 13.5 8C13.7761 8 14 7.77614 14 7.5C14 5.01472 11.9853 3 9.5 3H2.20711L3.35355 1.85355ZM2 7.5C2 7.22386 1.77614 7 1.5 7C1.22386 7 1 7.22386 1 7.5C1 9.98528 3.01472 12 5.5 12H12.7929L11.6464 13.1464C11.4512 13.3417 11.4512 13.6583 11.6464 13.8536C11.8417 14.0488 12.1583 14.0488 12.3536 13.8536L14.3536 11.8536C14.5488 11.6583 14.5488 11.3417 14.3536 11.1464L12.3536 9.14645C12.1583 8.95118 11.8417 8.95118 11.6464 9.14645C11.4512 9.34171 11.4512 9.65829 11.6464 9.85355L12.7929 11H5.5C3.567 11 2 9.433 2 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/magic-wand.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/magnifying-glass.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/margin.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/mask-off.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 2H14V13H1L1 2ZM0 2C0 1.44772 0.447715 1 1 1H14C14.5523 1 15 1.44772 15 2V13C15 13.5523 14.5523 14 14 14H1C0.447715 14 0 13.5523 0 13V2ZM4.875 7.5C4.875 6.05025 6.05025 4.875 7.5 4.875C8.94975 4.875 10.125 6.05025 10.125 7.5C10.125 8.94975 8.94975 10.125 7.5 10.125C6.05025 10.125 4.875 8.94975 4.875 7.5ZM7.5 3.875C5.49797 3.875 3.875 5.49797 3.875 7.5C3.875 9.50203 5.49797 11.125 7.5 11.125C9.50203 11.125 11.125 9.50203 11.125 7.5C11.125 5.49797 9.50203 3.875 7.5 3.875Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/mask-on.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 1C0.447715 1 0 1.44772 0 2V13C0 13.5523 0.447715 14 1 14H14C14.5523 14 15 13.5523 15 13V2C15 1.44772 14.5523 1 14 1H1ZM7.5 10.625C9.22589 10.625 10.625 9.22589 10.625 7.5C10.625 5.77411 9.22589 4.375 7.5 4.375C5.77411 4.375 4.375 5.77411 4.375 7.5C4.375 9.22589 5.77411 10.625 7.5 10.625Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/minus-circled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.876892C3.84222 0.876892 0.877075 3.84204 0.877075 7.49972C0.877075 11.1574 3.84222 14.1226 7.49991 14.1226C11.1576 14.1226 14.1227 11.1574 14.1227 7.49972C14.1227 3.84204 11.1576 0.876892 7.49991 0.876892ZM1.82707 7.49972C1.82707 4.36671 4.36689 1.82689 7.49991 1.82689C10.6329 1.82689 13.1727 4.36671 13.1727 7.49972C13.1727 10.6327 10.6329 13.1726 7.49991 13.1726C4.36689 13.1726 1.82707 10.6327 1.82707 7.49972ZM4.50003 7C4.22389 7 4.00003 7.22386 4.00003 7.5C4.00003 7.77614 4.22389 8 4.50003 8H10.5C10.7762 8 11 7.77614 11 7.5C11 7.22386 10.7762 7 10.5 7H4.50003Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/minus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.25 7.5C2.25 7.22386 2.47386 7 2.75 7H12.25C12.5261 7 12.75 7.22386 12.75 7.5C12.75 7.77614 12.5261 8 12.25 8H2.75C2.47386 8 2.25 7.77614 2.25 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/mix.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/mobile.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4 2.5C4 2.22386 4.22386 2 4.5 2H10.5C10.7761 2 11 2.22386 11 2.5V12.5C11 12.7761 10.7761 13 10.5 13H4.5C4.22386 13 4 12.7761 4 12.5V2.5ZM4.5 1C3.67157 1 3 1.67157 3 2.5V12.5C3 13.3284 3.67157 14 4.5 14H10.5C11.3284 14 12 13.3284 12 12.5V2.5C12 1.67157 11.3284 1 10.5 1H4.5ZM6 11.65C5.8067 11.65 5.65 11.8067 5.65 12C5.65 12.1933 5.8067 12.35 6 12.35H9C9.1933 12.35 9.35 12.1933 9.35 12C9.35 11.8067 9.1933 11.65 9 11.65H6Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/modulz-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.25925 3.16667L4.37036 5.33333V1L7.25925 3.16667ZM1 8.22222L3.88889 6.05555L1 3.88889V8.22222ZM1 14L3.88889 11.8333L1 9.66666V14ZM7.74072 8.22222L10.6296 6.05555L7.74072 3.88889V8.22222ZM14 3.16667L11.1111 5.33333V1L14 3.16667ZM11.1111 11.1111L14 8.94444L11.1111 6.77777V11.1111ZM3.88889 11.1111L1 8.94444L3.88889 6.77777V11.1111ZM4.37036 6.05555L7.25925 8.22222V3.88889L4.37036 6.05555ZM3.88889 5.33333L1 3.16667L3.88889 1V5.33333ZM7.74072 3.16667L10.6296 5.33333V1L7.74072 3.16667ZM14 8.22222L11.1111 6.05555L14 3.88889V8.22222ZM11.1111 11.8333L14 14V9.66666L11.1111 11.8333Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/moon.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/move.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/opacity.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 1.5C4.5 4.25 3 6.5 3 9C3 11.4853 5.01472 13.5 7.5 13.5C9.98528 13.5 12 11.4853 12 9C12 6.5 10.5 4.25 7.5 1.5ZM11 9C11 7.11203 9.97315 5.27195 7.5 2.87357C5.02686 5.27195 4 7.11203 4 9C4 10.933 5.567 12.5 7.5 12.5C9.433 12.5 11 10.933 11 9Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/open-in-new-window.svg πŸ”—

@@ -0,0 +1,10 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13 12C13 12.5523 12.5523 13 12 13H8.5C8.22386 13 8 12.7761 8 12.5C8 12.2239 8.22386 12 8.5 12H12V3H3V6.5C3 6.77614 2.77614 7 2.5 7C2.22386 7 2 6.77614 2 6.5V3C2 2.44771 2.44771 2 3 2H12C12.5523 2 13 2.44771 13 3V12Z"
+    fill="currentColor"
+  />
+  <path d="M5.5 6.5H8.5V9.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
+  <path d="M2.5 12.5L8.5 6.5" stroke="currentColor" stroke-linecap="round" />
+</svg>

assets/icons/radix/outer-shadow.svg πŸ”—

@@ -0,0 +1,43 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.1398 3.88616C13.8553 4.94159 15 6.837 15 8.99999C15 12.3137 12.3137 15 9.00001 15C6.8435 15 4.95295 13.8621 3.89569 12.1552L4.32075 11.8919C5.29069 13.4578 7.02375 14.5 9.00001 14.5C12.0375 14.5 14.5 12.0375 14.5 8.99999C14.5 7.0178 13.4516 5.28026 11.8778 4.31202L12.1398 3.88616Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.851 5.0732C13.8683 6.07105 14.5 7.46198 14.5 8.99999C14.5 12.0375 12.0375 14.5 8.99996 14.5C7.46208 14.5 6.07125 13.8685 5.07342 12.8512L5.43036 12.5011C6.33803 13.4264 7.60179 14 8.99996 14C11.7614 14 14 11.7614 14 8.99999C14 7.6017 13.4263 6.33785 12.5009 5.43017L12.851 5.0732Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3021 6.45071C13.7455 7.19737 14 8.06934 14 9C14 11.7614 11.7614 14 9.00001 14C8.04867 14 7.15867 13.7341 6.40118 13.2723L6.66141 12.8454C7.34274 13.2607 8.14305 13.5 9.00001 13.5C11.4853 13.5 13.5 11.4853 13.5 9C13.5 8.16164 13.271 7.37753 12.8722 6.70598L13.3021 6.45071Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3744 7.94021C13.4566 8.2803 13.5 8.63524 13.5 9C13.5 11.4853 11.4853 13.5 9.00002 13.5C8.61103 13.5 8.23321 13.4506 7.87267 13.3576L7.99758 12.8734C8.31767 12.956 8.65352 13 9.00002 13C11.2091 13 13 11.2091 13 9C13 8.67507 12.9613 8.35952 12.8884 8.05756L13.3744 7.94021Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".65"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.9155 9.82132C12.5898 11.3813 11.3562 12.6072 9.79203 12.9215L9.69353 12.4313C11.0613 12.1565 12.1413 11.0833 12.4261 9.71913L12.9155 9.82132Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.2771 7.50252C1.2771 4.06455 4.06413 1.27753 7.50209 1.27753C10.94 1.27753 13.7271 4.06455 13.7271 7.50252C13.7271 10.9405 10.94 13.7275 7.50209 13.7275C4.06412 13.7275 1.2771 10.9405 1.2771 7.50252ZM7.50209 2.22752C4.5888 2.22752 2.2271 4.58922 2.2271 7.50252C2.2271 10.4158 4.5888 12.7775 7.50209 12.7775C10.4154 12.7775 12.7771 10.4158 12.7771 7.50252C12.7771 4.58922 10.4154 2.22752 7.50209 2.22752Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/overline.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.49985 1.10001C3.27894 1.10001 3.09985 1.27909 3.09985 1.50001C3.09985 1.72092 3.27894 1.90001 3.49985 1.90001H11.4999C11.7208 1.90001 11.8999 1.72092 11.8999 1.50001C11.8999 1.27909 11.7208 1.10001 11.4999 1.10001H3.49985ZM4.99995 4.25001C4.99995 3.97387 4.77609 3.75001 4.49995 3.75001C4.22381 3.75001 3.99995 3.97387 3.99995 4.25001V9.55001C3.99995 11.483 5.56695 13.05 7.49995 13.05C9.43295 13.05 11 11.483 11 9.55001V4.25001C11 3.97387 10.7761 3.75001 10.5 3.75001C10.2238 3.75001 9.99995 3.97387 9.99995 4.25001V9.55001C9.99995 10.9307 8.88066 12.05 7.49995 12.05C6.11924 12.05 4.99995 10.9307 4.99995 9.55001V4.25001Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/padding.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/paper-plane.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.20308 1.04312C1.00481 0.954998 0.772341 1.0048 0.627577 1.16641C0.482813 1.32802 0.458794 1.56455 0.568117 1.75196L3.92115 7.50002L0.568117 13.2481C0.458794 13.4355 0.482813 13.672 0.627577 13.8336C0.772341 13.9952 1.00481 14.045 1.20308 13.9569L14.7031 7.95693C14.8836 7.87668 15 7.69762 15 7.50002C15 7.30243 14.8836 7.12337 14.7031 7.04312L1.20308 1.04312ZM4.84553 7.10002L2.21234 2.586L13.2689 7.50002L2.21234 12.414L4.84552 7.90002H9C9.22092 7.90002 9.4 7.72094 9.4 7.50002C9.4 7.27911 9.22092 7.10002 9 7.10002H4.84553Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pause.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.04995 2.74998C6.04995 2.44623 5.80371 2.19998 5.49995 2.19998C5.19619 2.19998 4.94995 2.44623 4.94995 2.74998V12.25C4.94995 12.5537 5.19619 12.8 5.49995 12.8C5.80371 12.8 6.04995 12.5537 6.04995 12.25V2.74998ZM10.05 2.74998C10.05 2.44623 9.80371 2.19998 9.49995 2.19998C9.19619 2.19998 8.94995 2.44623 8.94995 2.74998V12.25C8.94995 12.5537 9.19619 12.8 9.49995 12.8C9.80371 12.8 10.05 12.5537 10.05 12.25V2.74998Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pencil-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.8536 1.14645C11.6583 0.951184 11.3417 0.951184 11.1465 1.14645L3.71455 8.57836C3.62459 8.66832 3.55263 8.77461 3.50251 8.89155L2.04044 12.303C1.9599 12.491 2.00189 12.709 2.14646 12.8536C2.29103 12.9981 2.50905 13.0401 2.69697 12.9596L6.10847 11.4975C6.2254 11.4474 6.3317 11.3754 6.42166 11.2855L13.8536 3.85355C14.0488 3.65829 14.0488 3.34171 13.8536 3.14645L11.8536 1.14645ZM4.42166 9.28547L11.5 2.20711L12.7929 3.5L5.71455 10.5784L4.21924 11.2192L3.78081 10.7808L4.42166 9.28547Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pencil-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/person.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 0.875C5.49797 0.875 3.875 2.49797 3.875 4.5C3.875 6.15288 4.98124 7.54738 6.49373 7.98351C5.2997 8.12901 4.27557 8.55134 3.50407 9.31167C2.52216 10.2794 2.02502 11.72 2.02502 13.5999C2.02502 13.8623 2.23769 14.0749 2.50002 14.0749C2.76236 14.0749 2.97502 13.8623 2.97502 13.5999C2.97502 11.8799 3.42786 10.7206 4.17091 9.9883C4.91536 9.25463 6.02674 8.87499 7.49995 8.87499C8.97317 8.87499 10.0846 9.25463 10.8291 9.98831C11.5721 10.7206 12.025 11.8799 12.025 13.5999C12.025 13.8623 12.2376 14.0749 12.5 14.0749C12.7623 14.075 12.975 13.8623 12.975 13.6C12.975 11.72 12.4778 10.2794 11.4959 9.31166C10.7244 8.55135 9.70025 8.12903 8.50625 7.98352C10.0187 7.5474 11.125 6.15289 11.125 4.5C11.125 2.49797 9.50203 0.875 7.5 0.875ZM4.825 4.5C4.825 3.02264 6.02264 1.825 7.5 1.825C8.97736 1.825 10.175 3.02264 10.175 4.5C10.175 5.97736 8.97736 7.175 7.5 7.175C6.02264 7.175 4.825 5.97736 4.825 4.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pie-chart.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.85001 7.50043C1.85001 4.37975 4.37963 1.85001 7.50001 1.85001C10.6204 1.85001 13.15 4.37975 13.15 7.50043C13.15 10.6211 10.6204 13.1509 7.50001 13.1509C4.37963 13.1509 1.85001 10.6211 1.85001 7.50043ZM7.50001 0.850006C3.82728 0.850006 0.850006 3.82753 0.850006 7.50043C0.850006 11.1733 3.82728 14.1509 7.50001 14.1509C11.1727 14.1509 14.15 11.1733 14.15 7.50043C14.15 3.82753 11.1727 0.850006 7.50001 0.850006ZM7.00001 8.00001V3.12811C7.16411 3.10954 7.33094 3.10001 7.50001 3.10001C9.93006 3.10001 11.9 5.07014 11.9 7.50043C11.9 7.66935 11.8905 7.83604 11.872 8.00001H7.00001Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pilcrow.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3 5.5C3 7.983 4.99169 9 7 9V12.5C7 12.7761 7.22386 13 7.5 13C7.77614 13 8 12.7761 8 12.5V9V3.1H9V12.5C9 12.7761 9.22386 13 9.5 13C9.77614 13 10 12.7761 10 12.5V3.1H11.5C11.8038 3.1 12.05 2.85376 12.05 2.55C12.05 2.24624 11.8038 2 11.5 2H9.5H8H7.5H7C4.99169 2 3 3.017 3 5.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pin-bottom.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.5 13.95C13.7485 13.95 13.95 13.7485 13.95 13.5C13.95 13.2514 13.7485 13.05 13.5 13.05L1.49995 13.05C1.25142 13.05 1.04995 13.2514 1.04995 13.5C1.04995 13.7485 1.25142 13.95 1.49995 13.95L13.5 13.95ZM11.0681 7.5683C11.2439 7.39257 11.2439 7.10764 11.0681 6.93191C10.8924 6.75617 10.6075 6.75617 10.4317 6.93191L7.94993 9.41371L7.94993 1.49998C7.94993 1.25146 7.74846 1.04998 7.49993 1.04998C7.2514 1.04998 7.04993 1.25146 7.04993 1.49998L7.04993 9.41371L4.56813 6.93191C4.39239 6.75617 4.10746 6.75617 3.93173 6.93191C3.75599 7.10764 3.75599 7.39257 3.93173 7.5683L7.18173 10.8183C7.35746 10.994 7.64239 10.994 7.81812 10.8183L11.0681 7.5683Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pin-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.05005 13.5C2.05005 13.7485 2.25152 13.95 2.50005 13.95C2.74858 13.95 2.95005 13.7485 2.95005 13.5L2.95005 1.49995C2.95005 1.25142 2.74858 1.04995 2.50005 1.04995C2.25152 1.04995 2.05005 1.25142 2.05005 1.49995L2.05005 13.5ZM8.4317 11.0681C8.60743 11.2439 8.89236 11.2439 9.06809 11.0681C9.24383 10.8924 9.24383 10.6075 9.06809 10.4317L6.58629 7.94993L14.5 7.94993C14.7485 7.94993 14.95 7.74846 14.95 7.49993C14.95 7.2514 14.7485 7.04993 14.5 7.04993L6.58629 7.04993L9.06809 4.56813C9.24383 4.39239 9.24383 4.10746 9.06809 3.93173C8.89236 3.75599 8.60743 3.75599 8.4317 3.93173L5.1817 7.18173C5.00596 7.35746 5.00596 7.64239 5.1817 7.81812L8.4317 11.0681Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pin-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.95 1.50005C12.95 1.25152 12.7485 1.05005 12.5 1.05005C12.2514 1.05005 12.05 1.25152 12.05 1.50005L12.05 13.5C12.05 13.7486 12.2514 13.95 12.5 13.95C12.7485 13.95 12.95 13.7486 12.95 13.5L12.95 1.50005ZM6.5683 3.93188C6.39257 3.75614 6.10764 3.75614 5.93191 3.93188C5.75617 4.10761 5.75617 4.39254 5.93191 4.56827L8.41371 7.05007L0.499984 7.05007C0.251456 7.05007 0.0499847 7.25155 0.0499847 7.50007C0.0499846 7.7486 0.251457 7.95007 0.499984 7.95007L8.41371 7.95007L5.93191 10.4319C5.75617 10.6076 5.75617 10.8925 5.93191 11.0683C6.10764 11.244 6.39257 11.244 6.56831 11.0683L9.8183 7.81827C9.99404 7.64254 9.99404 7.35761 9.8183 7.18188L6.5683 3.93188Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/pin-top.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.50005 1.05005C1.25152 1.05005 1.05005 1.25152 1.05005 1.50005C1.05005 1.74858 1.25152 1.95005 1.50005 1.95005L13.5 1.95005C13.7486 1.95005 13.95 1.74858 13.95 1.50005C13.95 1.25152 13.7486 1.05005 13.5 1.05005H1.50005ZM3.93188 7.43169C3.75614 7.60743 3.75614 7.89236 3.93188 8.06809C4.10761 8.24383 4.39254 8.24383 4.56827 8.06809L7.05007 5.58629V13.5C7.05007 13.7485 7.25155 13.95 7.50007 13.95C7.7486 13.95 7.95007 13.7485 7.95007 13.5L7.95007 5.58629L10.4319 8.06809C10.6076 8.24383 10.8925 8.24383 11.0683 8.06809C11.244 7.89235 11.244 7.60743 11.0683 7.43169L7.81827 4.18169C7.64254 4.00596 7.35761 4.00596 7.18188 4.18169L3.93188 7.43169Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/play.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.24182 2.32181C3.3919 2.23132 3.5784 2.22601 3.73338 2.30781L12.7334 7.05781C12.8974 7.14436 13 7.31457 13 7.5C13 7.68543 12.8974 7.85564 12.7334 7.94219L3.73338 12.6922C3.5784 12.774 3.3919 12.7687 3.24182 12.6782C3.09175 12.5877 3 12.4252 3 12.25V2.75C3 2.57476 3.09175 2.4123 3.24182 2.32181ZM4 3.57925V11.4207L11.4288 7.5L4 3.57925Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/plus-circled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.876892C3.84222 0.876892 0.877075 3.84204 0.877075 7.49972C0.877075 11.1574 3.84222 14.1226 7.49991 14.1226C11.1576 14.1226 14.1227 11.1574 14.1227 7.49972C14.1227 3.84204 11.1576 0.876892 7.49991 0.876892ZM1.82707 7.49972C1.82707 4.36671 4.36689 1.82689 7.49991 1.82689C10.6329 1.82689 13.1727 4.36671 13.1727 7.49972C13.1727 10.6327 10.6329 13.1726 7.49991 13.1726C4.36689 13.1726 1.82707 10.6327 1.82707 7.49972ZM7.50003 4C7.77617 4 8.00003 4.22386 8.00003 4.5V7H10.5C10.7762 7 11 7.22386 11 7.5C11 7.77614 10.7762 8 10.5 8H8.00003V10.5C8.00003 10.7761 7.77617 11 7.50003 11C7.22389 11 7.00003 10.7761 7.00003 10.5V8H4.50003C4.22389 8 4.00003 7.77614 4.00003 7.5C4.00003 7.22386 4.22389 7 4.50003 7H7.00003V4.5C7.00003 4.22386 7.22389 4 7.50003 4Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/plus.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8 2.75C8 2.47386 7.77614 2.25 7.5 2.25C7.22386 2.25 7 2.47386 7 2.75V7H2.75C2.47386 7 2.25 7.22386 2.25 7.5C2.25 7.77614 2.47386 8 2.75 8H7V12.25C7 12.5261 7.22386 12.75 7.5 12.75C7.77614 12.75 8 12.5261 8 12.25V8H12.25C12.5261 8 12.75 7.77614 12.75 7.5C12.75 7.22386 12.5261 7 12.25 7H8V2.75Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/question-mark.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.07505 4.10001C5.07505 2.91103 6.25727 1.92502 7.50005 1.92502C8.74283 1.92502 9.92505 2.91103 9.92505 4.10001C9.92505 5.19861 9.36782 5.71436 8.61854 6.37884L8.58757 6.4063C7.84481 7.06467 6.92505 7.87995 6.92505 9.5C6.92505 9.81757 7.18248 10.075 7.50005 10.075C7.81761 10.075 8.07505 9.81757 8.07505 9.5C8.07505 8.41517 8.62945 7.90623 9.38156 7.23925L9.40238 7.22079C10.1496 6.55829 11.075 5.73775 11.075 4.10001C11.075 2.12757 9.21869 0.775024 7.50005 0.775024C5.7814 0.775024 3.92505 2.12757 3.92505 4.10001C3.92505 4.41758 4.18249 4.67501 4.50005 4.67501C4.81761 4.67501 5.07505 4.41758 5.07505 4.10001ZM7.50005 13.3575C7.9833 13.3575 8.37505 12.9657 8.37505 12.4825C8.37505 11.9992 7.9833 11.6075 7.50005 11.6075C7.0168 11.6075 6.62505 11.9992 6.62505 12.4825C6.62505 12.9657 7.0168 13.3575 7.50005 13.3575Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/quote.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/radiobutton.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49985 0.877045C3.84216 0.877045 0.877014 3.84219 0.877014 7.49988C0.877014 11.1575 3.84216 14.1227 7.49985 14.1227C11.1575 14.1227 14.1227 11.1575 14.1227 7.49988C14.1227 3.84219 11.1575 0.877045 7.49985 0.877045ZM1.82701 7.49988C1.82701 4.36686 4.36683 1.82704 7.49985 1.82704C10.6328 1.82704 13.1727 4.36686 13.1727 7.49988C13.1727 10.6329 10.6328 13.1727 7.49985 13.1727C4.36683 13.1727 1.82701 10.6329 1.82701 7.49988ZM7.49999 9.49999C8.60456 9.49999 9.49999 8.60456 9.49999 7.49999C9.49999 6.39542 8.60456 5.49999 7.49999 5.49999C6.39542 5.49999 5.49999 6.39542 5.49999 7.49999C5.49999 8.60456 6.39542 9.49999 7.49999 9.49999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/reader.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/reload.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.84998 7.49998C1.84998 4.66458 4.05979 1.84998 7.49998 1.84998C10.2783 1.84998 11.6515 3.9064 12.2367 5H10.5C10.2239 5 10 5.22386 10 5.5C10 5.77614 10.2239 6 10.5 6H13.5C13.7761 6 14 5.77614 14 5.5V2.5C14 2.22386 13.7761 2 13.5 2C13.2239 2 13 2.22386 13 2.5V4.31318C12.2955 3.07126 10.6659 0.849976 7.49998 0.849976C3.43716 0.849976 0.849976 4.18537 0.849976 7.49998C0.849976 10.8146 3.43716 14.15 7.49998 14.15C9.44382 14.15 11.0622 13.3808 12.2145 12.2084C12.8315 11.5806 13.3133 10.839 13.6418 10.0407C13.7469 9.78536 13.6251 9.49315 13.3698 9.38806C13.1144 9.28296 12.8222 9.40478 12.7171 9.66014C12.4363 10.3425 12.0251 10.9745 11.5013 11.5074C10.5295 12.4963 9.16504 13.15 7.49998 13.15C4.05979 13.15 1.84998 10.3354 1.84998 7.49998Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/reset.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.85355 2.14645C5.04882 2.34171 5.04882 2.65829 4.85355 2.85355L3.70711 4H9C11.4853 4 13.5 6.01472 13.5 8.5C13.5 10.9853 11.4853 13 9 13H5C4.72386 13 4.5 12.7761 4.5 12.5C4.5 12.2239 4.72386 12 5 12H9C10.933 12 12.5 10.433 12.5 8.5C12.5 6.567 10.933 5 9 5H3.70711L4.85355 6.14645C5.04882 6.34171 5.04882 6.65829 4.85355 6.85355C4.65829 7.04882 4.34171 7.04882 4.14645 6.85355L2.14645 4.85355C1.95118 4.65829 1.95118 4.34171 2.14645 4.14645L4.14645 2.14645C4.34171 1.95118 4.65829 1.95118 4.85355 2.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/resume.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.04995 2.74995C3.04995 2.44619 2.80371 2.19995 2.49995 2.19995C2.19619 2.19995 1.94995 2.44619 1.94995 2.74995V12.25C1.94995 12.5537 2.19619 12.8 2.49995 12.8C2.80371 12.8 3.04995 12.5537 3.04995 12.25V2.74995ZM5.73333 2.30776C5.57835 2.22596 5.39185 2.23127 5.24177 2.32176C5.0917 2.41225 4.99995 2.57471 4.99995 2.74995V12.25C4.99995 12.4252 5.0917 12.5877 5.24177 12.6781C5.39185 12.7686 5.57835 12.7739 5.73333 12.6921L14.7333 7.94214C14.8973 7.85559 15 7.68539 15 7.49995C15 7.31452 14.8973 7.14431 14.7333 7.05776L5.73333 2.30776ZM5.99995 11.4207V3.5792L13.4287 7.49995L5.99995 11.4207Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/rocket.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/rotate-counter-clockwise.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.59664 2.93628C7.76085 3.06401 8.00012 2.94698 8.00012 2.73895V1.99998C9.98143 2 11.1848 2.3637 11.9105 3.08945C12.6363 3.81522 13 5.0186 13 6.99998C13 7.27613 13.2239 7.49998 13.5 7.49998C13.7761 7.49998 14 7.27613 14 6.99998C14 4.9438 13.6325 3.39719 12.6176 2.38234C11.6028 1.36752 10.0562 0.999999 8.00012 0.999984V0.261266C8.00012 0.0532293 7.76085 -0.0637944 7.59664 0.063928L6.00384 1.30277C5.87516 1.40286 5.87516 1.59735 6.00384 1.69744L7.59664 2.93628ZM9.5 5H2.5C2.22386 5 2 5.22386 2 5.5V12.5C2 12.7761 2.22386 13 2.5 13H9.5C9.77614 13 10 12.7761 10 12.5V5.5C10 5.22386 9.77614 5 9.5 5ZM2.5 4C1.67157 4 1 4.67157 1 5.5V12.5C1 13.3284 1.67157 14 2.5 14H9.5C10.3284 14 11 13.3284 11 12.5V5.5C11 4.67157 10.3284 4 9.5 4H2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/row-spacing.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/rows.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14 12.85L1 12.85L1 14.15L14 14.15L14 12.85ZM14 8.85002L1 8.85002L1 10.15L14 10.15L14 8.85002ZM1 4.85003L14 4.85003L14 6.15003L1 6.15002L1 4.85003ZM14 0.850025L1 0.850025L1 2.15002L14 2.15002L14 0.850025Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/ruler-horizontal.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.5 4C0.223858 4 0 4.22386 0 4.5V10.5C0 10.7761 0.223858 11 0.5 11H14.5C14.7761 11 15 10.7761 15 10.5V4.5C15 4.22386 14.7761 4 14.5 4H0.5ZM1 10V5H2.075V7.5C2.075 7.73472 2.26528 7.925 2.5 7.925C2.73472 7.925 2.925 7.73472 2.925 7.5V5H4.075V6.5C4.075 6.73472 4.26528 6.925 4.5 6.925C4.73472 6.925 4.925 6.73472 4.925 6.5V5H6.075V6.5C6.075 6.73472 6.26528 6.925 6.5 6.925C6.73472 6.925 6.925 6.73472 6.925 6.5V5H8.075V7.5C8.075 7.73472 8.26528 7.925 8.5 7.925C8.73472 7.925 8.925 7.73472 8.925 7.5V5H10.075V6.5C10.075 6.73472 10.2653 6.925 10.5 6.925C10.7347 6.925 10.925 6.73472 10.925 6.5V5H12.075V6.5C12.075 6.73472 12.2653 6.925 12.5 6.925C12.7347 6.925 12.925 6.73472 12.925 6.5V5H14V10H1Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/scissors.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/section.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/sewing-pin-filled.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10 3.5C10 4.70948 9.14112 5.71836 8 5.94999V13.5C8 13.7761 7.77614 14 7.5 14C7.22386 14 7 13.7761 7 13.5V5.94999C5.85888 5.71836 5 4.70948 5 3.5C5 2.11929 6.11929 1 7.5 1C8.88071 1 10 2.11929 10 3.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/sewing-pin-solid.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10 3.5C10 4.70948 9.14112 5.71836 8 5.94999V13.5C8 13.7761 7.77614 14 7.5 14C7.22386 14 7 13.7761 7 13.5V5.94999C5.85888 5.71836 5 4.70948 5 3.5C5 2.11929 6.11929 1 7.5 1C8.88071 1 10 2.11929 10 3.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/sewing-pin.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6 3.5C6 2.67157 6.67157 2 7.5 2C8.32843 2 9 2.67157 9 3.5C9 4.32843 8.32843 5 7.5 5C6.67157 5 6 4.32843 6 3.5ZM8 5.94999C9.14112 5.71836 10 4.70948 10 3.5C10 2.11929 8.88071 1 7.5 1C6.11929 1 5 2.11929 5 3.5C5 4.70948 5.85888 5.71836 7 5.94999V13.5C7 13.7761 7.22386 14 7.5 14C7.77614 14 8 13.7761 8 13.5V5.94999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/shadow-inner.svg πŸ”—

@@ -0,0 +1,78 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.1619 3.85182C8.35817 4.88918 4.88936 8.358 3.85199 12.1617L3.3696 12.0301C4.45356 8.05564 8.05581 4.45339 12.0303 3.36943L12.1619 3.85182Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".1"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.8807 3.42707C8.03441 4.50542 4.50561 8.03422 3.42726 11.8805L2.94582 11.7456C4.07129 7.73121 7.7314 4.0711 11.7458 2.94563L11.8807 3.42707Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".15"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.5201 3.02556C7.69092 4.16199 4.16779 7.68323 3.02805 11.512L2.54883 11.3694C3.73676 7.37869 7.38659 3.73076 11.3778 2.54623L11.5201 3.02556Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.0468 2.66169C7.31117 3.87664 3.87918 7.3079 2.66298 11.0434L2.18754 10.8886C3.45324 7.00109 7.00445 3.45062 10.8921 2.18621L11.0468 2.66169Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".25"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.5201 2.32365C6.92091 3.61447 3.62391 6.90876 2.32845 10.5073L1.858 10.338C3.20398 6.59909 6.61155 3.19424 10.3513 1.85301L10.5201 2.32365Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".3"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.90222 2.03122C6.50003 3.39465 3.39968 6.49367 2.03399 9.89551L1.56998 9.70924C2.98651 6.18076 6.18728 2.98133 9.71622 1.5671L9.90222 2.03122Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.20727 1.78873C6.06136 3.20349 3.21103 6.05203 1.79331 9.19738L1.33747 8.99192C2.80536 5.73528 5.74485 2.7976 9.0022 1.33272L9.20727 1.78873Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".4"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.40713 1.62085C5.59323 3.05117 3.05794 5.58509 1.62544 8.39847L1.17987 8.1716C2.66036 5.26397 5.27232 2.6534 8.18057 1.17513L8.40713 1.62085Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".45"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.46207 1.56747C5.08689 2.94695 2.95362 5.07912 1.57249 7.45379L1.14028 7.20241C2.56503 4.75273 4.7607 2.55818 7.21096 1.1351L7.46207 1.56747Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.30407 1.70487C4.51964 2.91063 2.90983 4.52061 1.7043 6.30513L1.28998 6.02524C2.5313 4.18773 4.18673 2.53214 6.02413 1.29059L6.30407 1.70487Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49988C0.877075 3.84219 3.84222 0.877045 7.49991 0.877045C11.1576 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49988ZM7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 4.36686 10.6329 1.82704 7.49991 1.82704Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/shadow-none.svg πŸ”—

@@ -0,0 +1,78 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.78296 13.376C8.73904 9.95284 8.73904 5.04719 6.78296 1.62405L7.21708 1.37598C9.261 4.95283 9.261 10.0472 7.21708 13.624L6.78296 13.376Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".1"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.28204 13.4775C9.23929 9.99523 9.23929 5.00475 7.28204 1.52248L7.71791 1.2775C9.76067 4.9119 9.76067 10.0881 7.71791 13.7225L7.28204 13.4775Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".15"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.82098 13.5064C9.72502 9.99523 9.72636 5.01411 7.82492 1.50084L8.26465 1.26285C10.2465 4.92466 10.2451 10.085 8.26052 13.7448L7.82098 13.5064Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.41284 13.429C10.1952 9.92842 10.1957 5.07537 8.41435 1.57402L8.85999 1.34729C10.7139 4.99113 10.7133 10.0128 8.85841 13.6559L8.41284 13.429Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".25"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.02441 13.2956C10.6567 9.8379 10.6586 5.17715 9.03005 1.71656L9.48245 1.50366C11.1745 5.09919 11.1726 9.91629 9.47657 13.5091L9.02441 13.2956Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".3"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.66809 13.0655C11.1097 9.69572 11.1107 5.3121 9.67088 1.94095L10.1307 1.74457C11.6241 5.24121 11.6231 9.76683 10.1278 13.2622L9.66809 13.0655Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.331 12.7456C11.5551 9.52073 11.5564 5.49103 10.3347 2.26444L10.8024 2.0874C12.0672 5.42815 12.0659 9.58394 10.7985 12.9231L10.331 12.7456Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".4"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.0155 12.2986C11.9938 9.29744 11.9948 5.71296 11.0184 2.71067L11.4939 2.55603C12.503 5.6589 12.502 9.35178 11.4909 12.4535L11.0155 12.2986Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".45"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.7214 11.668C12.4254 9.01303 12.4262 5.99691 11.7237 3.34116L12.2071 3.21329C12.9318 5.95292 12.931 9.05728 12.2047 11.7961L11.7214 11.668Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.4432 10.752C12.8524 8.63762 12.8523 6.36089 12.4429 4.2466L12.9338 4.15155C13.3553 6.32861 13.3554 8.66985 12.9341 10.847L12.4432 10.752Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49991 0.877045C3.84222 0.877045 0.877075 3.84219 0.877075 7.49988C0.877075 9.1488 1.47969 10.657 2.4767 11.8162L1.64647 12.6464C1.45121 12.8417 1.45121 13.1583 1.64647 13.3535C1.84173 13.5488 2.15832 13.5488 2.35358 13.3535L3.18383 12.5233C4.34302 13.5202 5.8511 14.1227 7.49991 14.1227C11.1576 14.1227 14.1227 11.1575 14.1227 7.49988C14.1227 5.85107 13.5202 4.34298 12.5233 3.1838L13.3536 2.35355C13.5488 2.15829 13.5488 1.8417 13.3536 1.64644C13.1583 1.45118 12.8417 1.45118 12.6465 1.64644L11.8162 2.47667C10.657 1.47966 9.14883 0.877045 7.49991 0.877045ZM11.1423 3.15065C10.1568 2.32449 8.88644 1.82704 7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 8.88641 2.32452 10.1568 3.15069 11.1422L11.1423 3.15065ZM3.85781 11.8493C4.84322 12.6753 6.11348 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 6.11345 12.6754 4.84319 11.8493 3.85778L3.85781 11.8493Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/shadow-outer.svg πŸ”—

@@ -0,0 +1,43 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.1398 3.88617C13.8553 4.94159 15 6.83701 15 9.00001C15 12.3137 12.3137 15 9.00002 15C6.84351 15 4.95296 13.8621 3.89569 12.1552L4.32076 11.8919C5.29069 13.4578 7.02376 14.5 9.00002 14.5C12.0376 14.5 14.5 12.0375 14.5 9.00001C14.5 7.01781 13.4516 5.28027 11.8778 4.31203L12.1398 3.88617Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.851 5.07321C13.8684 6.07106 14.5 7.46199 14.5 9C14.5 12.0375 12.0376 14.5 9.00004 14.5C7.46215 14.5 6.07132 13.8685 5.07349 12.8513L5.43043 12.5011C6.3381 13.4264 7.60186 14 9.00004 14C11.7614 14 14 11.7614 14 9C14 7.60171 13.4264 6.33786 12.5009 5.43017L12.851 5.07321Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3022 6.45071C13.7455 7.19737 14 8.06935 14 9.00001C14 11.7614 11.7614 14 9.00002 14C8.04868 14 7.15868 13.7341 6.40118 13.2724L6.66142 12.8454C7.34275 13.2607 8.14306 13.5 9.00002 13.5C11.4853 13.5 13.5 11.4853 13.5 9.00001C13.5 8.16165 13.271 7.37754 12.8722 6.70599L13.3022 6.45071Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3745 7.94022C13.4566 8.28031 13.5 8.63525 13.5 9.00001C13.5 11.4853 11.4853 13.5 9.00003 13.5C8.61104 13.5 8.23323 13.4506 7.87268 13.3576L7.99759 12.8734C8.31768 12.956 8.65353 13 9.00003 13C11.2091 13 13 11.2091 13 9.00001C13 8.67509 12.9613 8.35953 12.8884 8.05757L13.3745 7.94022Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".65"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.9155 9.82133C12.5898 11.3813 11.3562 12.6072 9.79205 12.9215L9.69354 12.4313C11.0613 12.1565 12.1413 11.0834 12.4261 9.71915L12.9155 9.82133Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.2771 7.50253C1.2771 4.06456 4.06413 1.27753 7.5021 1.27753C10.94 1.27753 13.7271 4.06456 13.7271 7.50253C13.7271 10.9405 10.94 13.7275 7.5021 13.7275C4.06413 13.7275 1.2771 10.9405 1.2771 7.50253ZM7.5021 2.22753C4.5888 2.22753 2.2271 4.58923 2.2271 7.50253C2.2271 10.4158 4.5888 12.7775 7.5021 12.7775C10.4154 12.7775 12.7771 10.4158 12.7771 7.50253C12.7771 4.58923 10.4154 2.22753 7.5021 2.22753Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/shadow.svg πŸ”—

@@ -0,0 +1,78 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".05"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.78296 13.376C8.73904 9.95284 8.73904 5.04719 6.78296 1.62405L7.21708 1.37598C9.261 4.95283 9.261 10.0472 7.21708 13.624L6.78296 13.376Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".1"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.28204 13.4775C9.23929 9.99523 9.23929 5.00475 7.28204 1.52248L7.71791 1.2775C9.76067 4.9119 9.76067 10.0881 7.71791 13.7225L7.28204 13.4775Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".15"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.82098 13.5064C9.72502 9.99523 9.72636 5.01411 7.82492 1.50084L8.26465 1.26285C10.2465 4.92466 10.2451 10.085 8.26052 13.7448L7.82098 13.5064Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".2"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8.41284 13.429C10.1952 9.92842 10.1957 5.07537 8.41435 1.57402L8.85999 1.34729C10.7139 4.99113 10.7133 10.0128 8.85841 13.6559L8.41284 13.429Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".25"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.02441 13.2956C10.6567 9.8379 10.6586 5.17715 9.03005 1.71656L9.48245 1.50366C11.1745 5.09919 11.1726 9.91629 9.47657 13.5091L9.02441 13.2956Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".3"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M9.66809 13.0655C11.1097 9.69572 11.1107 5.3121 9.67088 1.94095L10.1307 1.74457C11.6241 5.24121 11.6231 9.76683 10.1278 13.2622L9.66809 13.0655Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".35"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.331 12.7456C11.5551 9.52073 11.5564 5.49103 10.3347 2.26444L10.8024 2.0874C12.0672 5.42815 12.0659 9.58394 10.7985 12.9231L10.331 12.7456Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".4"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.0155 12.2986C11.9938 9.29744 11.9948 5.71296 11.0184 2.71067L11.4939 2.55603C12.503 5.6589 12.502 9.35178 11.4909 12.4535L11.0155 12.2986Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".45"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.7214 11.668C12.4254 9.01303 12.4262 5.99691 11.7237 3.34116L12.2071 3.21329C12.9318 5.95292 12.931 9.05728 12.2047 11.7961L11.7214 11.668Z"
+    fill="currentColor"
+  />
+  <path
+    opacity=".5"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M12.4432 10.752C12.8524 8.63762 12.8523 6.36089 12.4429 4.2466L12.9338 4.15155C13.3553 6.32861 13.3554 8.66985 12.9341 10.847L12.4432 10.752Z"
+    fill="currentColor"
+  />
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49988C0.877075 3.84219 3.84222 0.877045 7.49991 0.877045C11.1576 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49988ZM7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 4.36686 10.6329 1.82704 7.49991 1.82704Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/share-1.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/share-2.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/shuffle.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/size.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M11.5 3.04999C11.7485 3.04999 11.95 3.25146 11.95 3.49999V7.49999C11.95 7.74852 11.7485 7.94999 11.5 7.94999C11.2515 7.94999 11.05 7.74852 11.05 7.49999V4.58639L4.58638 11.05H7.49999C7.74852 11.05 7.94999 11.2515 7.94999 11.5C7.94999 11.7485 7.74852 11.95 7.49999 11.95L3.49999 11.95C3.38064 11.95 3.26618 11.9026 3.18179 11.8182C3.0974 11.7338 3.04999 11.6193 3.04999 11.5L3.04999 7.49999C3.04999 7.25146 3.25146 7.04999 3.49999 7.04999C3.74852 7.04999 3.94999 7.25146 3.94999 7.49999L3.94999 10.4136L10.4136 3.94999L7.49999 3.94999C7.25146 3.94999 7.04999 3.74852 7.04999 3.49999C7.04999 3.25146 7.25146 3.04999 7.49999 3.04999L11.5 3.04999Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/sketch-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/slash.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.10876 14L9.46582 1H10.8178L5.46074 14H4.10876Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/slider.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.3004 7.49991C10.3004 8.4943 9.49426 9.30041 8.49988 9.30041C7.50549 9.30041 6.69938 8.4943 6.69938 7.49991C6.69938 6.50553 7.50549 5.69942 8.49988 5.69942C9.49426 5.69942 10.3004 6.50553 10.3004 7.49991ZM11.205 8C10.9699 9.28029 9.84816 10.2504 8.49988 10.2504C7.1516 10.2504 6.0299 9.28029 5.79473 8H0.5C0.223858 8 0 7.77614 0 7.5C0 7.22386 0.223858 7 0.5 7H5.7947C6.0298 5.71962 7.15154 4.74942 8.49988 4.74942C9.84822 4.74942 10.97 5.71962 11.2051 7H14.5C14.7761 7 15 7.22386 15 7.5C15 7.77614 14.7761 8 14.5 8H11.205Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/space-between-horizontally.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14.4999 0.999994C14.2237 0.999994 13.9999 1.22385 13.9999 1.49999L13.9999 5.99995L9.99992 5.99995C9.44764 5.99995 8.99993 6.44766 8.99993 6.99994L8.99993 7.99994C8.99993 8.55222 9.44764 8.99993 9.99992 8.99993L13.9999 8.99993L13.9999 13.4999C13.9999 13.776 14.2237 13.9999 14.4999 13.9999C14.776 13.9999 14.9999 13.776 14.9999 13.4999L14.9999 1.49999C14.9999 1.22385 14.776 0.999994 14.4999 0.999994ZM4.99996 5.99995L0.999992 5.99995L0.999992 1.49999C0.999992 1.22385 0.776136 0.999994 0.499996 0.999994C0.223856 0.999994 -9.7852e-09 1.22385 -2.18557e-08 1.49999L4.07279e-07 13.4999C3.95208e-07 13.776 0.223855 13.9999 0.499996 13.9999C0.776136 13.9999 0.999992 13.776 0.999992 13.4999L0.999992 8.99993L4.99996 8.99993C5.55224 8.99993 5.99995 8.55222 5.99995 7.99993L5.99995 6.99994C5.99995 6.44766 5.55224 5.99995 4.99996 5.99995Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/space-between-vertically.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.999878 0.5C0.999878 0.223858 1.22374 0 1.49988 0H13.4999C13.776 0 13.9999 0.223858 13.9999 0.5C13.9999 0.776142 13.776 1 13.4999 1L9 1V5C9 5.55228 8.55228 6 8 6H7C6.44772 6 6 5.55228 6 5V1H1.49988C1.22374 1 0.999878 0.776142 0.999878 0.5ZM7 9C6.44772 9 6 9.44771 6 10V14H1.49988C1.22374 14 0.999878 14.2239 0.999878 14.5C0.999878 14.7761 1.22374 15 1.49988 15H13.4999C13.776 15 13.9999 14.7761 13.9999 14.5C13.9999 14.2239 13.776 14 13.4999 14H9V10C9 9.44772 8.55228 9 8 9H7Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/space-evenly-vertically.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.999878 0.5C0.999878 0.223858 1.22374 0 1.49988 0H13.4999C13.776 0 13.9999 0.223858 13.9999 0.5C13.9999 0.776142 13.776 1 13.4999 1H1.49988C1.22374 1 0.999878 0.776142 0.999878 0.5ZM7 2C6.44772 2 6 2.44772 6 3V6C6 6.55228 6.44772 7 7 7H8C8.55228 7 9 6.55228 9 6V3C9 2.44772 8.55228 2 8 2H7ZM7 8C6.44772 8 6 8.44771 6 9V12C6 12.5523 6.44772 13 7 13H8C8.55228 13 9 12.5523 9 12V9C9 8.44772 8.55228 8 8 8H7ZM1.49988 14C1.22374 14 0.999878 14.2239 0.999878 14.5C0.999878 14.7761 1.22374 15 1.49988 15H13.4999C13.776 15 13.9999 14.7761 13.9999 14.5C13.9999 14.2239 13.776 14 13.4999 14H1.49988Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/speaker-moderate.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8 1.5C8 1.31062 7.893 1.13749 7.72361 1.05279C7.55421 0.968093 7.35151 0.986371 7.2 1.1L3.33333 4H1.5C0.671573 4 0 4.67158 0 5.5V9.5C0 10.3284 0.671573 11 1.5 11H3.33333L7.2 13.9C7.35151 14.0136 7.55421 14.0319 7.72361 13.9472C7.893 13.8625 8 13.6894 8 13.5V1.5ZM3.8 4.9L7 2.5V12.5L3.8 10.1C3.71345 10.0351 3.60819 10 3.5 10H1.5C1.22386 10 1 9.77614 1 9.5V5.5C1 5.22386 1.22386 5 1.5 5H3.5C3.60819 5 3.71345 4.96491 3.8 4.9ZM10.833 3.95949C10.7106 3.77557 10.4623 3.72567 10.2784 3.84804C10.0944 3.97041 10.0445 4.21871 10.1669 4.40264C11.4111 6.27268 11.4111 8.72728 10.1669 10.5973C10.0445 10.7813 10.0944 11.0296 10.2784 11.1519C10.4623 11.2743 10.7106 11.2244 10.833 11.0405C12.2558 8.90199 12.2558 6.09798 10.833 3.95949Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/speaker-off.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.72361 1.05279C7.893 1.13749 8 1.31062 8 1.5V13.5C8 13.6894 7.893 13.8625 7.72361 13.9472C7.55421 14.0319 7.35151 14.0136 7.2 13.9L3.33333 11H1.5C0.671573 11 0 10.3284 0 9.5V5.5C0 4.67158 0.671573 4 1.5 4H3.33333L7.2 1.1C7.35151 0.986371 7.55421 0.968093 7.72361 1.05279ZM7 2.5L3.8 4.9C3.71345 4.96491 3.60819 5 3.5 5H1.5C1.22386 5 1 5.22386 1 5.5V9.5C1 9.77614 1.22386 10 1.5 10H3.5C3.60819 10 3.71345 10.0351 3.8 10.1L7 12.5V2.5ZM14.8536 5.14645C15.0488 5.34171 15.0488 5.65829 14.8536 5.85355L13.2071 7.5L14.8536 9.14645C15.0488 9.34171 15.0488 9.65829 14.8536 9.85355C14.6583 10.0488 14.3417 10.0488 14.1464 9.85355L12.5 8.20711L10.8536 9.85355C10.6583 10.0488 10.3417 10.0488 10.1464 9.85355C9.95118 9.65829 9.95118 9.34171 10.1464 9.14645L11.7929 7.5L10.1464 5.85355C9.95118 5.65829 9.95118 5.34171 10.1464 5.14645C10.3417 4.95118 10.6583 4.95118 10.8536 5.14645L12.5 6.79289L14.1464 5.14645C14.3417 4.95118 14.6583 4.95118 14.8536 5.14645Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/speaker-quiet.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8 1.5C8 1.31062 7.893 1.13749 7.72361 1.05279C7.55421 0.968093 7.35151 0.986371 7.2 1.1L3.33333 4H1.5C0.671573 4 0 4.67158 0 5.5V9.5C0 10.3284 0.671573 11 1.5 11H3.33333L7.2 13.9C7.35151 14.0136 7.55421 14.0319 7.72361 13.9472C7.893 13.8625 8 13.6894 8 13.5V1.5ZM3.8 4.9L7 2.5V12.5L3.8 10.1C3.71345 10.0351 3.60819 10 3.5 10H1.5C1.22386 10 1 9.77614 1 9.5V5.5C1 5.22386 1.22386 5 1.5 5H3.5C3.60819 5 3.71345 4.96491 3.8 4.9ZM10.083 5.05577C9.96066 4.87185 9.71235 4.82195 9.52843 4.94432C9.3445 5.06669 9.2946 5.31499 9.41697 5.49892C10.2207 6.70693 10.2207 8.29303 9.41697 9.50104C9.2946 9.68496 9.3445 9.93326 9.52843 10.0556C9.71235 10.178 9.96066 10.1281 10.083 9.94418C11.0653 8.46773 11.0653 6.53222 10.083 5.05577Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/square.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 1H1.5H13.5H14V1.5V13.5V14H13.5H1.5H1V13.5V1.5V1ZM2 2V13H13V2H2Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/stack.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.75432 1.81954C7.59742 1.72682 7.4025 1.72682 7.24559 1.81954L1.74559 5.06954C1.59336 5.15949 1.49996 5.32317 1.49996 5.5C1.49996 5.67683 1.59336 5.84051 1.74559 5.93046L7.24559 9.18046C7.4025 9.27318 7.59742 9.27318 7.75432 9.18046L13.2543 5.93046C13.4066 5.84051 13.5 5.67683 13.5 5.5C13.5 5.32317 13.4066 5.15949 13.2543 5.06954L7.75432 1.81954ZM7.49996 8.16923L2.9828 5.5L7.49996 2.83077L12.0171 5.5L7.49996 8.16923ZM2.25432 8.31954C2.01658 8.17906 1.70998 8.2579 1.56949 8.49564C1.42901 8.73337 1.50785 9.03998 1.74559 9.18046L7.24559 12.4305C7.4025 12.5232 7.59742 12.5232 7.75432 12.4305L13.2543 9.18046C13.4921 9.03998 13.5709 8.73337 13.4304 8.49564C13.2899 8.2579 12.9833 8.17906 12.7456 8.31954L7.49996 11.4192L2.25432 8.31954Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/star-filled.svg πŸ”—

@@ -0,0 +1,6 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    d="M7.22303 0.665992C7.32551 0.419604 7.67454 0.419604 7.77702 0.665992L9.41343 4.60039C9.45663 4.70426 9.55432 4.77523 9.66645 4.78422L13.914 5.12475C14.18 5.14607 14.2878 5.47802 14.0852 5.65162L10.849 8.42374C10.7636 8.49692 10.7263 8.61176 10.7524 8.72118L11.7411 12.866C11.803 13.1256 11.5206 13.3308 11.2929 13.1917L7.6564 10.9705C7.5604 10.9119 7.43965 10.9119 7.34365 10.9705L3.70718 13.1917C3.47945 13.3308 3.19708 13.1256 3.25899 12.866L4.24769 8.72118C4.2738 8.61176 4.23648 8.49692 4.15105 8.42374L0.914889 5.65162C0.712228 5.47802 0.820086 5.14607 1.08608 5.12475L5.3336 4.78422C5.44573 4.77523 5.54342 4.70426 5.58662 4.60039L7.22303 0.665992Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/star.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/stop.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 3C2 2.44772 2.44772 2 3 2H12C12.5523 2 13 2.44772 13 3V12C13 12.5523 12.5523 13 12 13H3C2.44772 13 2 12.5523 2 12V3ZM12 3H3V12H12V3Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/stopwatch.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.49998 0.5C5.49998 0.223858 5.72383 0 5.99998 0H7.49998H8.99998C9.27612 0 9.49998 0.223858 9.49998 0.5C9.49998 0.776142 9.27612 1 8.99998 1H7.99998V2.11922C9.09832 2.20409 10.119 2.56622 10.992 3.13572C11.0116 3.10851 11.0336 3.08252 11.058 3.05806L11.858 2.25806C12.1021 2.01398 12.4978 2.01398 12.7419 2.25806C12.986 2.50214 12.986 2.89786 12.7419 3.14194L11.967 3.91682C13.1595 5.07925 13.9 6.70314 13.9 8.49998C13.9 12.0346 11.0346 14.9 7.49998 14.9C3.96535 14.9 1.09998 12.0346 1.09998 8.49998C1.09998 5.13362 3.69904 2.3743 6.99998 2.11922V1H5.99998C5.72383 1 5.49998 0.776142 5.49998 0.5ZM2.09998 8.49998C2.09998 5.51764 4.51764 3.09998 7.49998 3.09998C10.4823 3.09998 12.9 5.51764 12.9 8.49998C12.9 11.4823 10.4823 13.9 7.49998 13.9C4.51764 13.9 2.09998 11.4823 2.09998 8.49998ZM7.99998 4.5C7.99998 4.22386 7.77612 4 7.49998 4C7.22383 4 6.99998 4.22386 6.99998 4.5V9.5C6.99998 9.77614 7.22383 10 7.49998 10C7.77612 10 7.99998 9.77614 7.99998 9.5V4.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/stretch-horizontally.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14.4999 0.999992C14.2237 0.999992 13.9999 1.22385 13.9999 1.49999L13.9999 5.99995L0.999992 5.99995L0.999992 1.49999C0.999992 1.22385 0.776136 0.999992 0.499996 0.999992C0.223856 0.999992 -9.78509e-09 1.22385 -2.18556e-08 1.49999L4.07279e-07 13.4999C3.95208e-07 13.776 0.223855 13.9999 0.499996 13.9999C0.776136 13.9999 0.999992 13.776 0.999992 13.4999L0.999992 8.99992L13.9999 8.99992L13.9999 13.4999C13.9999 13.776 14.2237 13.9999 14.4999 13.9999C14.776 13.9999 14.9999 13.776 14.9999 13.4999L14.9999 1.49999C14.9999 1.22385 14.776 0.999992 14.4999 0.999992Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/stretch-vertically.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.999878 0.5C0.999878 0.223858 1.22374 0 1.49988 0H13.4999C13.776 0 13.9999 0.223858 13.9999 0.5C13.9999 0.776142 13.776 1 13.4999 1H6H1.49988C1.22374 1 0.999878 0.776142 0.999878 0.5ZM9 14V1L6 1V14H1.49988C1.22374 14 0.999878 14.2239 0.999878 14.5C0.999878 14.7761 1.22374 15 1.49988 15H13.4999C13.776 15 13.9999 14.7761 13.9999 14.5C13.9999 14.2239 13.776 14 13.4999 14H9Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/strikethrough.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.00003 3.25C5.00003 2.97386 4.77617 2.75 4.50003 2.75C4.22389 2.75 4.00003 2.97386 4.00003 3.25V7.10003H2.49998C2.27906 7.10003 2.09998 7.27912 2.09998 7.50003C2.09998 7.72094 2.27906 7.90003 2.49998 7.90003H4.00003V8.55C4.00003 10.483 5.56703 12.05 7.50003 12.05C9.43303 12.05 11 10.483 11 8.55V7.90003H12.5C12.7209 7.90003 12.9 7.72094 12.9 7.50003C12.9 7.27912 12.7209 7.10003 12.5 7.10003H11V3.25C11 2.97386 10.7762 2.75 10.5 2.75C10.2239 2.75 10 2.97386 10 3.25V7.10003H5.00003V3.25ZM5.00003 7.90003V8.55C5.00003 9.93071 6.11932 11.05 7.50003 11.05C8.88074 11.05 10 9.93071 10 8.55V7.90003H5.00003Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/sun.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/switch.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10.5 4C8.567 4 7 5.567 7 7.5C7 9.433 8.567 11 10.5 11C12.433 11 14 9.433 14 7.5C14 5.567 12.433 4 10.5 4ZM7.67133 11C6.65183 10.175 6 8.91363 6 7.5C6 6.08637 6.65183 4.82498 7.67133 4H4.5C2.567 4 1 5.567 1 7.5C1 9.433 2.567 11 4.5 11H7.67133ZM0 7.5C0 5.01472 2.01472 3 4.5 3H10.5C12.9853 3 15 5.01472 15 7.5C15 9.98528 12.9853 12 10.5 12H4.5C2.01472 12 0 9.98528 0 7.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/symbol.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/table.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8 2H12.5C12.7761 2 13 2.22386 13 2.5V5H8V2ZM7 5V2H2.5C2.22386 2 2 2.22386 2 2.5V5H7ZM2 6V9H7V6H2ZM8 6H13V9H8V6ZM8 10H13V12.5C13 12.7761 12.7761 13 12.5 13H8V10ZM2 12.5V10H7V13H2.5C2.22386 13 2 12.7761 2 12.5ZM1 2.5C1 1.67157 1.67157 1 2.5 1H12.5C13.3284 1 14 1.67157 14 2.5V12.5C14 13.3284 13.3284 14 12.5 14H2.5C1.67157 14 1 13.3284 1 12.5V2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/target.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/text-align-center.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 4.5C2 4.22386 2.22386 4 2.5 4H12.5C12.7761 4 13 4.22386 13 4.5C13 4.77614 12.7761 5 12.5 5H2.5C2.22386 5 2 4.77614 2 4.5ZM4 7.5C4 7.22386 4.22386 7 4.5 7H10.5C10.7761 7 11 7.22386 11 7.5C11 7.77614 10.7761 8 10.5 8H4.5C4.22386 8 4 7.77614 4 7.5ZM3 10.5C3 10.2239 3.22386 10 3.5 10H11.5C11.7761 10 12 10.2239 12 10.5C12 10.7761 11.7761 11 11.5 11H3.5C3.22386 11 3 10.7761 3 10.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/text-align-justify.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2.5 4C2.22386 4 2 4.22386 2 4.5C2 4.77614 2.22386 5 2.5 5H12.5C12.7761 5 13 4.77614 13 4.5C13 4.22386 12.7761 4 12.5 4H2.5ZM2 7.5C2 7.22386 2.22386 7 2.5 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H2.5C2.22386 8 2 7.77614 2 7.5ZM2 10.5C2 10.2239 2.22386 10 2.5 10H12.5C12.7761 10 13 10.2239 13 10.5C13 10.7761 12.7761 11 12.5 11H2.5C2.22386 11 2 10.7761 2 10.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/text-align-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 4.5C2 4.22386 2.22386 4 2.5 4H12.5C12.7761 4 13 4.22386 13 4.5C13 4.77614 12.7761 5 12.5 5H2.5C2.22386 5 2 4.77614 2 4.5ZM2 7.5C2 7.22386 2.22386 7 2.5 7H7.5C7.77614 7 8 7.22386 8 7.5C8 7.77614 7.77614 8 7.5 8H2.5C2.22386 8 2 7.77614 2 7.5ZM2 10.5C2 10.2239 2.22386 10 2.5 10H10.5C10.7761 10 11 10.2239 11 10.5C11 10.7761 10.7761 11 10.5 11H2.5C2.22386 11 2 10.7761 2 10.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/text-align-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M2 4.5C2 4.22386 2.22386 4 2.5 4H12.5C12.7761 4 13 4.22386 13 4.5C13 4.77614 12.7761 5 12.5 5H2.5C2.22386 5 2 4.77614 2 4.5ZM7 7.5C7 7.22386 7.22386 7 7.5 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H7.5C7.22386 8 7 7.77614 7 7.5ZM4 10.5C4 10.2239 4.22386 10 4.5 10H12.5C12.7761 10 13 10.2239 13 10.5C13 10.7761 12.7761 11 12.5 11H4.5C4.22386 11 4 10.7761 4 10.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/text-none.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.3536 2.35355C13.5488 2.15829 13.5488 1.84171 13.3536 1.64645C13.1583 1.45118 12.8417 1.45118 12.6464 1.64645L11.9291 2.36383C11.9159 2.32246 11.897 2.28368 11.8732 2.24845C11.7923 2.12875 11.6554 2.05005 11.5001 2.05005H3.50005C3.29909 2.05005 3.1289 2.18178 3.07111 2.3636C3.05743 2.40665 3.05005 2.45249 3.05005 2.50007V4.50001C3.05005 4.74854 3.25152 4.95001 3.50005 4.95001C3.74858 4.95001 3.95005 4.74854 3.95005 4.50001V2.95005H6.95006V7.34284L1.64645 12.6464C1.45118 12.8417 1.45118 13.1583 1.64645 13.3536C1.84171 13.5488 2.15829 13.5488 2.35355 13.3536L6.95006 8.75705V12.0501H5.7544C5.50587 12.0501 5.3044 12.2515 5.3044 12.5001C5.3044 12.7486 5.50587 12.9501 5.7544 12.9501H9.2544C9.50293 12.9501 9.7044 12.7486 9.7044 12.5001C9.7044 12.2515 9.50293 12.0501 9.2544 12.0501H8.05006V7.65705L13.3536 2.35355ZM8.05006 6.24284L11.0501 3.24283V2.95005H8.05006V6.24284Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/text.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M3.94993 2.95002L3.94993 4.49998C3.94993 4.74851 3.74845 4.94998 3.49993 4.94998C3.2514 4.94998 3.04993 4.74851 3.04993 4.49998V2.50004C3.04993 2.45246 3.05731 2.40661 3.07099 2.36357C3.12878 2.18175 3.29897 2.05002 3.49993 2.05002H11.4999C11.6553 2.05002 11.7922 2.12872 11.8731 2.24842C11.9216 2.32024 11.9499 2.40682 11.9499 2.50002L11.9499 2.50004V4.49998C11.9499 4.74851 11.7485 4.94998 11.4999 4.94998C11.2514 4.94998 11.0499 4.74851 11.0499 4.49998V2.95002H8.04993V12.05H9.25428C9.50281 12.05 9.70428 12.2515 9.70428 12.5C9.70428 12.7486 9.50281 12.95 9.25428 12.95H5.75428C5.50575 12.95 5.30428 12.7486 5.30428 12.5C5.30428 12.2515 5.50575 12.05 5.75428 12.05H6.94993V2.95002H3.94993Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/thick-arrow-down.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5 3.5C5 3.22386 5.22386 3 5.5 3H9.5C9.77614 3 10 3.22386 10 3.5V6H12.5C12.6873 6 12.8589 6.10467 12.9446 6.27121C13.0303 6.43774 13.0157 6.63821 12.9069 6.79062L7.90687 13.7906C7.81301 13.922 7.66148 14 7.5 14C7.33853 14 7.18699 13.922 7.09314 13.7906L2.09314 6.79062C1.98427 6.63821 1.96972 6.43774 2.05542 6.27121C2.14112 6.10467 2.31271 6 2.5 6H5V3.5ZM6 4V6.5C6 6.77614 5.77614 7 5.5 7H3.4716L7.5 12.6398L11.5284 7H9.5C9.22386 7 9 6.77614 9 6.5V4H6Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/thick-arrow-left.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1 7.5C1 7.66148 1.07798 7.81301 1.20938 7.90687L8.20938 12.9069C8.36179 13.0157 8.56226 13.0303 8.72879 12.9446C8.89533 12.8589 9 12.6873 9 12.5L9 10L11.5 10C11.7761 10 12 9.77614 12 9.5L12 5.5C12 5.22386 11.7761 5 11.5 5L9 5L9 2.5C9 2.31271 8.89533 2.14112 8.72879 2.05542C8.56226 1.96972 8.36179 1.98427 8.20938 2.09313L1.20938 7.09314C1.07798 7.18699 1 7.33853 1 7.5ZM8 3.4716L8 5.5C8 5.77614 8.22386 6 8.5 6L11 6L11 9L8.5 9C8.22386 9 8 9.22386 8 9.5L8 11.5284L2.36023 7.5L8 3.4716Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/thick-arrow-right.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14 7.5C14 7.66148 13.922 7.81301 13.7906 7.90687L6.79062 12.9069C6.63821 13.0157 6.43774 13.0303 6.27121 12.9446C6.10467 12.8589 6 12.6873 6 12.5L6 10L3.5 10C3.22386 10 3 9.77614 3 9.5L3 5.5C3 5.22386 3.22386 5 3.5 5L6 5L6 2.5C6 2.31271 6.10467 2.14112 6.27121 2.05542C6.43774 1.96972 6.63821 1.98427 6.79062 2.09313L13.7906 7.09314C13.922 7.18699 14 7.33853 14 7.5ZM7 3.4716L7 5.5C7 5.77614 6.77614 6 6.5 6L4 6L4 9L6.5 9C6.77614 9 7 9.22386 7 9.5L7 11.5284L12.6398 7.5L7 3.4716Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/thick-arrow-up.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.5 1C7.66148 1 7.81301 1.07798 7.90687 1.20938L12.9069 8.20938C13.0157 8.36179 13.0303 8.56226 12.9446 8.72879C12.8589 8.89533 12.6873 9 12.5 9H10V11.5C10 11.7761 9.77614 12 9.5 12H5.5C5.22386 12 5 11.7761 5 11.5V9H2.5C2.31271 9 2.14112 8.89533 2.05542 8.72879C1.96972 8.56226 1.98427 8.36179 2.09314 8.20938L7.09314 1.20938C7.18699 1.07798 7.33853 1 7.5 1ZM3.4716 8H5.5C5.77614 8 6 8.22386 6 8.5V11H9V8.5C9 8.22386 9.22386 8 9.5 8H11.5284L7.5 2.36023L3.4716 8Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/timer.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49998 0.849976C7.22383 0.849976 6.99998 1.07383 6.99998 1.34998V3.52234C6.99998 3.79848 7.22383 4.02234 7.49998 4.02234C7.77612 4.02234 7.99998 3.79848 7.99998 3.52234V1.8718C10.8862 2.12488 13.15 4.54806 13.15 7.49998C13.15 10.6204 10.6204 13.15 7.49998 13.15C4.37957 13.15 1.84998 10.6204 1.84998 7.49998C1.84998 6.10612 2.35407 4.83128 3.19049 3.8459C3.36919 3.63538 3.34339 3.31985 3.13286 3.14115C2.92234 2.96245 2.60681 2.98825 2.42811 3.19877C1.44405 4.35808 0.849976 5.86029 0.849976 7.49998C0.849976 11.1727 3.82728 14.15 7.49998 14.15C11.1727 14.15 14.15 11.1727 14.15 7.49998C14.15 3.82728 11.1727 0.849976 7.49998 0.849976ZM6.74049 8.08072L4.22363 4.57237C4.15231 4.47295 4.16346 4.33652 4.24998 4.25C4.33649 4.16348 4.47293 4.15233 4.57234 4.22365L8.08069 6.74051C8.56227 7.08599 8.61906 7.78091 8.19998 8.2C7.78089 8.61909 7.08597 8.56229 6.74049 8.08072Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/tokens.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.5 2C3.11929 2 2 3.11929 2 4.5C2 5.88072 3.11929 7 4.5 7C5.88072 7 7 5.88072 7 4.5C7 3.11929 5.88072 2 4.5 2ZM3 4.5C3 3.67157 3.67157 3 4.5 3C5.32843 3 6 3.67157 6 4.5C6 5.32843 5.32843 6 4.5 6C3.67157 6 3 5.32843 3 4.5ZM10.5 2C9.11929 2 8 3.11929 8 4.5C8 5.88072 9.11929 7 10.5 7C11.8807 7 13 5.88072 13 4.5C13 3.11929 11.8807 2 10.5 2ZM9 4.5C9 3.67157 9.67157 3 10.5 3C11.3284 3 12 3.67157 12 4.5C12 5.32843 11.3284 6 10.5 6C9.67157 6 9 5.32843 9 4.5ZM2 10.5C2 9.11929 3.11929 8 4.5 8C5.88072 8 7 9.11929 7 10.5C7 11.8807 5.88072 13 4.5 13C3.11929 13 2 11.8807 2 10.5ZM4.5 9C3.67157 9 3 9.67157 3 10.5C3 11.3284 3.67157 12 4.5 12C5.32843 12 6 11.3284 6 10.5C6 9.67157 5.32843 9 4.5 9ZM10.5 8C9.11929 8 8 9.11929 8 10.5C8 11.8807 9.11929 13 10.5 13C11.8807 13 13 11.8807 13 10.5C13 9.11929 11.8807 8 10.5 8ZM9 10.5C9 9.67157 9.67157 9 10.5 9C11.3284 9 12 9.67157 12 10.5C12 11.3284 11.3284 12 10.5 12C9.67157 12 9 11.3284 9 10.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/track-next.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M13.0502 2.74989C13.0502 2.44613 12.804 2.19989 12.5002 2.19989C12.1965 2.19989 11.9502 2.44613 11.9502 2.74989V7.2825C11.9046 7.18802 11.8295 7.10851 11.7334 7.05776L2.73338 2.30776C2.5784 2.22596 2.3919 2.23127 2.24182 2.32176C2.09175 2.41225 2 2.57471 2 2.74995V12.25C2 12.4252 2.09175 12.5877 2.24182 12.6781C2.3919 12.7686 2.5784 12.7739 2.73338 12.6921L11.7334 7.94214C11.8295 7.89139 11.9046 7.81188 11.9502 7.7174V12.2499C11.9502 12.5536 12.1965 12.7999 12.5002 12.7999C12.804 12.7999 13.0502 12.5536 13.0502 12.2499V2.74989ZM3 11.4207V3.5792L10.4288 7.49995L3 11.4207Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/track-previous.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.94976 2.74989C1.94976 2.44613 2.196 2.19989 2.49976 2.19989C2.80351 2.19989 3.04976 2.44613 3.04976 2.74989V7.2825C3.0954 7.18802 3.17046 7.10851 3.26662 7.05776L12.2666 2.30776C12.4216 2.22596 12.6081 2.23127 12.7582 2.32176C12.9083 2.41225 13 2.57471 13 2.74995V12.25C13 12.4252 12.9083 12.5877 12.7582 12.6781C12.6081 12.7686 12.4216 12.7739 12.2666 12.6921L3.26662 7.94214C3.17046 7.89139 3.0954 7.81188 3.04976 7.7174V12.2499C3.04976 12.5536 2.80351 12.7999 2.49976 12.7999C2.196 12.7999 1.94976 12.5536 1.94976 12.2499V2.74989ZM4.57122 7.49995L12 11.4207V3.5792L4.57122 7.49995Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/transform.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/transparency-grid.svg πŸ”—

@@ -0,0 +1,9 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    opacity=".25"
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0 0H3V3H0V0ZM6 3H3V6H0V9H3V12H0V15H3V12H6V15H9V12H12V15H15V12H12V9H15V6H12V3H15V0H12V3H9V0H6V3ZM6 6V3H9V6H6ZM6 9H3V6H6V9ZM9 9V6H12V9H9ZM9 9H6V12H9V9Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/trash.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.5 1C5.22386 1 5 1.22386 5 1.5C5 1.77614 5.22386 2 5.5 2H9.5C9.77614 2 10 1.77614 10 1.5C10 1.22386 9.77614 1 9.5 1H5.5ZM3 3.5C3 3.22386 3.22386 3 3.5 3H5H10H11.5C11.7761 3 12 3.22386 12 3.5C12 3.77614 11.7761 4 11.5 4H11V12C11 12.5523 10.5523 13 10 13H5C4.44772 13 4 12.5523 4 12V4L3.5 4C3.22386 4 3 3.77614 3 3.5ZM5 4H10V12H5V4Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/triangle-down.svg πŸ”—

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M4 6H11L7.5 10.5L4 6Z" fill="currentColor" />
+</svg>

assets/icons/radix/triangle-left.svg πŸ”—

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M9 4L9 11L4.5 7.5L9 4Z" fill="currentColor" />
+</svg>

assets/icons/radix/triangle-right.svg πŸ”—

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M6 11L6 4L10.5 7.5L6 11Z" fill="currentColor" />
+</svg>

assets/icons/radix/triangle-up.svg πŸ”—

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M4 9H11L7.5 4.5L4 9Z" fill="currentColor" />
+</svg>

assets/icons/radix/underline.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M5.00001 2.75C5.00001 2.47386 4.77615 2.25 4.50001 2.25C4.22387 2.25 4.00001 2.47386 4.00001 2.75V8.05C4.00001 9.983 5.56702 11.55 7.50001 11.55C9.43301 11.55 11 9.983 11 8.05V2.75C11 2.47386 10.7762 2.25 10.5 2.25C10.2239 2.25 10 2.47386 10 2.75V8.05C10 9.43071 8.88072 10.55 7.50001 10.55C6.1193 10.55 5.00001 9.43071 5.00001 8.05V2.75ZM3.49998 13.1001C3.27906 13.1001 3.09998 13.2791 3.09998 13.5001C3.09998 13.721 3.27906 13.9001 3.49998 13.9001H11.5C11.7209 13.9001 11.9 13.721 11.9 13.5001C11.9 13.2791 11.7209 13.1001 11.5 13.1001H3.49998Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/update.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/upload.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.81825 1.18188C7.64251 1.00615 7.35759 1.00615 7.18185 1.18188L4.18185 4.18188C4.00611 4.35762 4.00611 4.64254 4.18185 4.81828C4.35759 4.99401 4.64251 4.99401 4.81825 4.81828L7.05005 2.58648V9.49996C7.05005 9.74849 7.25152 9.94996 7.50005 9.94996C7.74858 9.94996 7.95005 9.74849 7.95005 9.49996V2.58648L10.1819 4.81828C10.3576 4.99401 10.6425 4.99401 10.8182 4.81828C10.994 4.64254 10.994 4.35762 10.8182 4.18188L7.81825 1.18188ZM2.5 9.99997C2.77614 9.99997 3 10.2238 3 10.5V12C3 12.5538 3.44565 13 3.99635 13H11.0012C11.5529 13 12 12.5528 12 12V10.5C12 10.2238 12.2239 9.99997 12.5 9.99997C12.7761 9.99997 13 10.2238 13 10.5V12C13 13.104 12.1062 14 11.0012 14H3.99635C2.89019 14 2 13.103 2 12V10.5C2 10.2238 2.22386 9.99997 2.5 9.99997Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/value-none.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49985 0.877045C3.84216 0.877045 0.877014 3.84219 0.877014 7.49988C0.877014 9.1488 1.47963 10.657 2.47665 11.8162L1.64643 12.6464C1.45117 12.8417 1.45117 13.1583 1.64643 13.3535C1.8417 13.5488 2.15828 13.5488 2.35354 13.3535L3.18377 12.5233C4.34296 13.5202 5.85104 14.1227 7.49985 14.1227C11.1575 14.1227 14.1227 11.1575 14.1227 7.49988C14.1227 5.85107 13.5202 4.34299 12.5233 3.1838L13.3535 2.35354C13.5488 2.15827 13.5488 1.84169 13.3535 1.64643C13.1583 1.45117 12.8417 1.45117 12.6464 1.64643L11.8162 2.47668C10.657 1.47966 9.14877 0.877045 7.49985 0.877045ZM11.1422 3.15066C10.1567 2.32449 8.88639 1.82704 7.49985 1.82704C4.36683 1.82704 1.82701 4.36686 1.82701 7.49988C1.82701 8.88642 2.32446 10.1568 3.15063 11.1422L11.1422 3.15066ZM3.85776 11.8493C4.84317 12.6753 6.11343 13.1727 7.49985 13.1727C10.6328 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 6.11346 12.6753 4.8432 11.8493 3.85779L3.85776 11.8493Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/value.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M0.877075 7.49988C0.877075 3.84219 3.84222 0.877045 7.49991 0.877045C11.1576 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1576 14.1227 7.49991 14.1227C3.84222 14.1227 0.877075 11.1575 0.877075 7.49988ZM7.49991 1.82704C4.36689 1.82704 1.82708 4.36686 1.82708 7.49988C1.82708 10.6329 4.36689 13.1727 7.49991 13.1727C10.6329 13.1727 13.1727 10.6329 13.1727 7.49988C13.1727 4.36686 10.6329 1.82704 7.49991 1.82704Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/vercel-logo.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7.49998 1L6.92321 2.00307L1.17498 12L0.599976 13H1.7535H13.2464H14.4L13.825 12L8.07674 2.00307L7.49998 1ZM7.49998 3.00613L2.3285 12H12.6714L7.49998 3.00613Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/video.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"

assets/icons/radix/view-grid.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M7 2H1.5C1.22386 2 1 2.22386 1 2.5V7H7V2ZM8 2V7H14V2.5C14 2.22386 13.7761 2 13.5 2H8ZM7 8H1V12.5C1 12.7761 1.22386 13 1.5 13H7V8ZM8 13V8H14V12.5C14 12.7761 13.7761 13 13.5 13H8ZM1.5 1C0.671573 1 0 1.67157 0 2.5V12.5C0 13.3284 0.671573 14 1.5 14H13.5C14.3284 14 15 13.3284 15 12.5V2.5C15 1.67157 14.3284 1 13.5 1H1.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/view-horizontal.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M1.5 2H13.5C13.7761 2 14 2.22386 14 2.5V7H1V2.5C1 2.22386 1.22386 2 1.5 2ZM1 8V12.5C1 12.7761 1.22386 13 1.5 13H13.5C13.7761 13 14 12.7761 14 12.5V8H1ZM0 2.5C0 1.67157 0.671573 1 1.5 1H13.5C14.3284 1 15 1.67157 15 2.5V12.5C15 13.3284 14.3284 14 13.5 14H1.5C0.671573 14 0 13.3284 0 12.5V2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/view-none.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M14 2.58711L1.85163 13H13.5C13.7761 13 14 12.7761 14 12.5V2.58711ZM0.762879 13.8067L0.825396 13.8796L0.854717 13.8545C1.05017 13.9478 1.26899 14 1.5 14H13.5C14.3284 14 15 13.3284 15 12.5V2.5C15 1.93949 14.6926 1.45078 14.2371 1.19331L14.1746 1.12037L14.1453 1.1455C13.9498 1.05222 13.731 1 13.5 1H1.5C0.671573 1 0 1.67157 0 2.5V12.5C0 13.0605 0.307435 13.5492 0.762879 13.8067ZM1 12.4129L13.1484 2H1.5C1.22386 2 1 2.22386 1 2.5V12.4129Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/view-vertical.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M8 2H13.5C13.7761 2 14 2.22386 14 2.5V12.5C14 12.7761 13.7761 13 13.5 13H8V2ZM7 2H1.5C1.22386 2 1 2.22386 1 2.5V12.5C1 12.7761 1.22386 13 1.5 13H7V2ZM0 2.5C0 1.67157 0.671573 1 1.5 1H13.5C14.3284 1 15 1.67157 15 2.5V12.5C15 13.3284 14.3284 14 13.5 14H1.5C0.671573 14 0 13.3284 0 12.5V2.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/width.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M4.81812 4.68161C4.99386 4.85734 4.99386 5.14227 4.81812 5.318L3.08632 7.0498H11.9135L10.1817 5.318C10.006 5.14227 10.006 4.85734 10.1817 4.68161C10.3575 4.50587 10.6424 4.50587 10.8181 4.68161L13.3181 7.18161C13.4939 7.35734 13.4939 7.64227 13.3181 7.818L10.8181 10.318C10.6424 10.4937 10.3575 10.4937 10.1817 10.318C10.006 10.1423 10.006 9.85734 10.1817 9.68161L11.9135 7.9498H3.08632L4.81812 9.68161C4.99386 9.85734 4.99386 10.1423 4.81812 10.318C4.64239 10.4937 4.35746 10.4937 4.18173 10.318L1.68173 7.818C1.50599 7.64227 1.50599 7.35734 1.68173 7.18161L4.18173 4.68161C4.35746 4.50587 4.64239 4.50587 4.81812 4.68161Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/zoom-in.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159ZM4.25 6.5C4.25 6.22386 4.47386 6 4.75 6H6V4.75C6 4.47386 6.22386 4.25 6.5 4.25C6.77614 4.25 7 4.47386 7 4.75V6H8.25C8.52614 6 8.75 6.22386 8.75 6.5C8.75 6.77614 8.52614 7 8.25 7H7V8.25C7 8.52614 6.77614 8.75 6.5 8.75C6.22386 8.75 6 8.52614 6 8.25V7H4.75C4.47386 7 4.25 6.77614 4.25 6.5Z"
+    fill="currentColor"
+  />
+</svg>

assets/icons/radix/zoom-out.svg πŸ”—

@@ -0,0 +1,8 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    fill-rule="evenodd"
+    clip-rule="evenodd"
+    d="M6.5 10C8.433 10 10 8.433 10 6.5C10 4.567 8.433 3 6.5 3C4.567 3 3 4.567 3 6.5C3 8.433 4.567 10 6.5 10ZM6.5 11C7.56251 11 8.53901 10.6318 9.30884 10.0159L12.1464 12.8536C12.3417 13.0488 12.6583 13.0488 12.8536 12.8536C13.0488 12.6583 13.0488 12.3417 12.8536 12.1464L10.0159 9.30884C10.6318 8.53901 11 7.56251 11 6.5C11 4.01472 8.98528 2 6.5 2C4.01472 2 2 4.01472 2 6.5C2 8.98528 4.01472 11 6.5 11ZM4.75 6C4.47386 6 4.25 6.22386 4.25 6.5C4.25 6.77614 4.47386 7 4.75 7H8.25C8.52614 7 8.75 6.77614 8.75 6.5C8.75 6.22386 8.52614 6 8.25 6H4.75Z"
+    fill="currentColor"
+  />
+</svg>

assets/keymaps/default.json πŸ”—

@@ -40,7 +40,8 @@
       "cmd-o": "workspace::Open",
       "alt-cmd-o": "projects::OpenRecent",
       "ctrl-~": "workspace::NewTerminal",
-      "ctrl-`": "terminal_panel::ToggleFocus"
+      "ctrl-`": "terminal_panel::ToggleFocus",
+      "shift-escape": "workspace::ToggleZoom"
     }
   },
   {
@@ -197,9 +198,17 @@
     }
   },
   {
-    "context": "AssistantEditor > Editor",
+    "context": "AssistantPanel",
+    "bindings": {
+      "cmd-g": "search::SelectNextMatch",
+      "cmd-shift-g": "search::SelectPrevMatch"
+    }
+  },
+  {
+    "context": "ConversationEditor > Editor",
     "bindings": {
       "cmd-enter": "assistant::Assist",
+      "cmd-s": "workspace::Save",
       "cmd->": "assistant::QuoteSelection",
       "shift-enter": "assistant::Split",
       "ctrl-r": "assistant::CycleMessageRole"
@@ -234,8 +243,7 @@
       "cmd-shift-g": "search::SelectPrevMatch",
       "alt-cmd-c": "search::ToggleCaseSensitive",
       "alt-cmd-w": "search::ToggleWholeWord",
-      "alt-cmd-r": "search::ToggleRegex",
-      "shift-escape": "workspace::ToggleZoom"
+      "alt-cmd-r": "search::ToggleRegex"
     }
   },
   // Bindings from VS Code
@@ -400,6 +408,7 @@
       "cmd-shift-p": "command_palette::Toggle",
       "cmd-shift-m": "diagnostics::Deploy",
       "cmd-shift-e": "project_panel::ToggleFocus",
+      "cmd-?": "assistant::ToggleFocus",
       "cmd-alt-s": "workspace::SaveAll",
       "cmd-k m": "language_selector::Toggle"
     }
@@ -411,6 +420,7 @@
       "ctrl-shift-k": "editor::DeleteLine",
       "cmd-shift-d": "editor::DuplicateLine",
       "cmd-shift-l": "editor::SplitSelectionIntoLines",
+      "ctrl-j": "editor::JoinLines",
       "ctrl-cmd-up": "editor::MoveLineUp",
       "ctrl-cmd-down": "editor::MoveLineDown",
       "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",

assets/keymaps/vim.json πŸ”—

@@ -1,6 +1,6 @@
 [
   {
-    "context": "Editor && VimControl && !VimWaiting",
+    "context": "Editor && VimControl && !VimWaiting && !menu",
     "bindings": {
       "g": [
         "vim::PushOperator",
@@ -25,11 +25,15 @@
         }
       ],
       "h": "vim::Left",
+      "left": "vim::Left",
       "backspace": "vim::Backspace",
       "j": "vim::Down",
+      "down": "vim::Down",
       "enter": "vim::NextLineStart",
       "k": "vim::Up",
+      "up": "vim::Up",
       "l": "vim::Right",
+      "right": "vim::Right",
       "$": "vim::EndOfLine",
       "shift-g": "vim::EndOfDocument",
       "w": "vim::NextWordStart",
@@ -54,10 +58,6 @@
         }
       ],
       "%": "vim::Matching",
-      "ctrl-y": [
-        "vim::Scroll",
-        "LineUp"
-      ],
       "f": [
         "vim::PushOperator",
         {
@@ -90,6 +90,8 @@
           }
         }
       ],
+      "ctrl-o": "pane::GoBack",
+      "ctrl-]": "editor::GoToDefinition",
       "escape": "editor::Cancel",
       "0": "vim::StartOfLine", // When no number operator present, use start of line motion
       "1": [
@@ -131,7 +133,7 @@
     }
   },
   {
-    "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
+    "context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting",
     "bindings": {
       "c": [
         "vim::PushOperator",
@@ -143,6 +145,7 @@
         "Delete"
       ],
       "shift-d": "vim::DeleteToEndOfLine",
+      "shift-j": "editor::JoinLines",
       "y": [
         "vim::PushOperator",
         "Yank"
@@ -165,6 +168,7 @@
       "^": "vim::FirstNonWhitespace",
       "o": "vim::InsertLineBelow",
       "shift-o": "vim::InsertLineAbove",
+      "~": "vim::ChangeCase",
       "v": [
         "vim::SwitchMode",
         {
@@ -184,37 +188,29 @@
       "p": "vim::Paste",
       "u": "editor::Undo",
       "ctrl-r": "editor::Redo",
-      "ctrl-o": "pane::GoBack",
       "/": [
         "buffer_search::Deploy",
         {
           "focus": true
         }
       ],
-      "ctrl-f": [
-        "vim::Scroll",
-        "PageDown"
-      ],
-      "ctrl-b": [
-        "vim::Scroll",
-        "PageUp"
-      ],
-      "ctrl-d": [
-        "vim::Scroll",
-        "HalfPageDown"
-      ],
-      "ctrl-u": [
-        "vim::Scroll",
-        "HalfPageUp"
-      ],
-      "ctrl-e": [
-        "vim::Scroll",
-        "LineDown"
-      ],
+      "ctrl-f": "vim::PageDown",
+      "pagedown": "vim::PageDown",
+      "ctrl-b": "vim::PageUp",
+      "pageup": "vim::PageUp",
+      "ctrl-d": "vim::ScrollDown",
+      "ctrl-u": "vim::ScrollUp",
+      "ctrl-e": "vim::LineDown",
+      "ctrl-y": "vim::LineUp",
       "r": [
         "vim::PushOperator",
         "Replace"
-      ]
+      ],
+      "s": "vim::Substitute",
+      "> >": "editor::Indent",
+      "< <": "editor::Outdent",
+      "ctrl-pagedown": "pane::ActivateNextItem",
+      "ctrl-pageup": "pane::ActivatePrevItem"
     }
   },
   {
@@ -231,6 +227,8 @@
     "bindings": {
       "g": "vim::StartOfDocument",
       "h": "editor::Hover",
+      "t": "pane::ActivateNextItem",
+      "shift-t": "pane::ActivatePrevItem",
       "escape": [
         "vim::SwitchMode",
         "Normal"
@@ -301,10 +299,14 @@
       "x": "vim::VisualDelete",
       "y": "vim::VisualYank",
       "p": "vim::VisualPaste",
+      "s": "vim::Substitute",
+      "~": "vim::ChangeCase",
       "r": [
         "vim::PushOperator",
         "Replace"
-      ]
+      ],
+      "> >": "editor::Indent",
+      "< <": "editor::Outdent"
     }
   },
   {

assets/settings/default.json πŸ”—

@@ -57,37 +57,37 @@
   "show_whitespaces": "selection",
   // Scrollbar related settings
   "scrollbar": {
-      // When to show the scrollbar in the editor.
-      // This setting can take four values:
-      //
-      // 1. Show the scrollbar if there's important information or
-      //    follow the system's configured behavior (default):
-      //   "auto"
-      // 2. Match the system's configured behavior:
-      //    "system"
-      // 3. Always show the scrollbar:
-      //    "always"
-      // 4. Never show the scrollbar:
-      //    "never"
-      "show": "auto",
-      // Whether to show git diff indicators in the scrollbar.
-      "git_diff": true
+    // When to show the scrollbar in the editor.
+    // This setting can take four values:
+    //
+    // 1. Show the scrollbar if there's important information or
+    //    follow the system's configured behavior (default):
+    //   "auto"
+    // 2. Match the system's configured behavior:
+    //    "system"
+    // 3. Always show the scrollbar:
+    //    "always"
+    // 4. Never show the scrollbar:
+    //    "never"
+    "show": "auto",
+    // Whether to show git diff indicators in the scrollbar.
+    "git_diff": true
   },
   "project_panel": {
-      // Whether to show the git status in the project panel.
-      "git_status": true,
-      // Where to dock project panel. Can be 'left' or 'right'.
-      "dock": "left",
-      // Default width of the project panel.
-      "default_width": 240
+    // Whether to show the git status in the project panel.
+    "git_status": true,
+    // Where to dock project panel. Can be 'left' or 'right'.
+    "dock": "left",
+    // Default width of the project panel.
+    "default_width": 240
   },
   "assistant": {
-      // Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
-      "dock": "right",
-      // Default width when the assistant is docked to the left or right.
-      "default_width": 450,
-      // Default height when the assistant is docked to the bottom.
-      "default_height": 320
+    // Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
+    "dock": "right",
+    // Default width when the assistant is docked to the left or right.
+    "default_width": 640,
+    // Default height when the assistant is docked to the bottom.
+    "default_height": 320
   },
   // Whether the screen sharing icon is shown in the os status bar.
   "show_call_status_icon": true,

crates/activity_indicator/src/activity_indicator.rs πŸ”—

@@ -321,7 +321,7 @@ impl View for ActivityIndicator {
         let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
             let theme = &theme::current(cx).workspace.status_bar.lsp_status;
             let style = if state.hovered() && on_click.is_some() {
-                theme.hover.as_ref().unwrap_or(&theme.default)
+                theme.hovered.as_ref().unwrap_or(&theme.default)
             } else {
                 &theme.default
             };

crates/ai/Cargo.toml πŸ”—

@@ -22,9 +22,10 @@ util = { path = "../util" }
 workspace = { path = "../workspace" }
 
 anyhow.workspace = true
-chrono = "0.4"
+chrono = { version = "0.4", features = ["serde"] }
 futures.workspace = true
 isahc.workspace = true
+regex.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true
@@ -33,3 +34,4 @@ tiktoken-rs = "0.4"
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }

crates/ai/src/ai.rs πŸ”—

@@ -1,10 +1,22 @@
 pub mod assistant;
 mod assistant_settings;
 
+use anyhow::Result;
 pub use assistant::AssistantPanel;
+use chrono::{DateTime, Local};
+use collections::HashMap;
+use fs::Fs;
+use futures::StreamExt;
 use gpui::AppContext;
+use regex::Regex;
 use serde::{Deserialize, Serialize};
-use std::fmt::{self, Display};
+use std::{
+    cmp::Reverse,
+    fmt::{self, Display},
+    path::PathBuf,
+    sync::Arc,
+};
+use util::paths::CONVERSATIONS_DIR;
 
 // Data types for chat completion requests
 #[derive(Debug, Serialize)]
@@ -14,6 +26,84 @@ struct OpenAIRequest {
     stream: bool,
 }
 
+#[derive(
+    Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
+)]
+struct MessageId(usize);
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+struct MessageMetadata {
+    role: Role,
+    sent_at: DateTime<Local>,
+    status: MessageStatus,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+enum MessageStatus {
+    Pending,
+    Done,
+    Error(Arc<str>),
+}
+
+#[derive(Serialize, Deserialize)]
+struct SavedMessage {
+    id: MessageId,
+    start: usize,
+}
+
+#[derive(Serialize, Deserialize)]
+struct SavedConversation {
+    zed: String,
+    version: String,
+    text: String,
+    messages: Vec<SavedMessage>,
+    message_metadata: HashMap<MessageId, MessageMetadata>,
+    summary: String,
+    model: String,
+}
+
+impl SavedConversation {
+    const VERSION: &'static str = "0.1.0";
+}
+
+struct SavedConversationMetadata {
+    title: String,
+    path: PathBuf,
+    mtime: chrono::DateTime<chrono::Local>,
+}
+
+impl SavedConversationMetadata {
+    pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
+        fs.create_dir(&CONVERSATIONS_DIR).await?;
+
+        let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
+        let mut conversations = Vec::<SavedConversationMetadata>::new();
+        while let Some(path) = paths.next().await {
+            let path = path?;
+
+            let pattern = r" - \d+.zed.json$";
+            let re = Regex::new(pattern).unwrap();
+
+            let metadata = fs.metadata(&path).await?;
+            if let Some((file_name, metadata)) = path
+                .file_name()
+                .and_then(|name| name.to_str())
+                .zip(metadata)
+            {
+                let title = re.replace(file_name, "");
+                conversations.push(Self {
+                    title: title.into_owned(),
+                    path,
+                    mtime: metadata.mtime.into(),
+                });
+            }
+        }
+        conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
+
+        Ok(conversations)
+    }
+}
+
 #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
 struct RequestMessage {
     role: Role,

crates/ai/src/assistant.rs πŸ”—

@@ -1,6 +1,7 @@
 use crate::{
     assistant_settings::{AssistantDockPosition, AssistantSettings},
-    OpenAIRequest, OpenAIResponseStreamEvent, RequestMessage, Role,
+    MessageId, MessageMetadata, MessageStatus, OpenAIRequest, OpenAIResponseStreamEvent,
+    RequestMessage, Role, SavedConversation, SavedConversationMetadata, SavedMessage,
 };
 use anyhow::{anyhow, Result};
 use chrono::{DateTime, Local};
@@ -23,17 +24,26 @@ use gpui::{
 };
 use isahc::{http::StatusCode, Request, RequestExt};
 use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
+use search::BufferSearchBar;
 use serde::Deserialize;
 use settings::SettingsStore;
 use std::{
-    borrow::Cow, cell::RefCell, cmp, fmt::Write, io, iter, ops::Range, rc::Rc, sync::Arc,
+    cell::RefCell,
+    cmp, env,
+    fmt::Write,
+    io, iter,
+    ops::Range,
+    path::{Path, PathBuf},
+    rc::Rc,
+    sync::Arc,
     time::Duration,
 };
-use util::{channel::ReleaseChannel, post_inc, truncate_and_trailoff, ResultExt, TryFutureExt};
+use theme::AssistantStyle;
+use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, Panel},
-    item::Item,
-    pane, Pane, Workspace,
+    searchable::Direction,
+    Save, ToggleZoom, Toolbar, Workspace,
 };
 
 const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
@@ -41,41 +51,39 @@ const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
 actions!(
     assistant,
     [
-        NewContext,
+        NewConversation,
         Assist,
         Split,
         CycleMessageRole,
         QuoteSelection,
         ToggleFocus,
-        ResetKey
+        ResetKey,
     ]
 );
 
 pub fn init(cx: &mut AppContext) {
-    if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Stable {
-        cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
-            filter.filtered_namespaces.insert("assistant");
-        });
-    }
-
     settings::register::<AssistantSettings>(cx);
     cx.add_action(
-        |workspace: &mut Workspace, _: &NewContext, cx: &mut ViewContext<Workspace>| {
-            if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
-                this.update(cx, |this, cx| this.add_context(cx))
-            }
-
-            workspace.focus_panel::<AssistantPanel>(cx);
+        |this: &mut AssistantPanel,
+         _: &workspace::NewFile,
+         cx: &mut ViewContext<AssistantPanel>| {
+            this.new_conversation(cx);
         },
     );
-    cx.add_action(AssistantEditor::assist);
-    cx.capture_action(AssistantEditor::cancel_last_assist);
-    cx.add_action(AssistantEditor::quote_selection);
-    cx.capture_action(AssistantEditor::copy);
-    cx.capture_action(AssistantEditor::split);
-    cx.capture_action(AssistantEditor::cycle_message_role);
+    cx.add_action(ConversationEditor::assist);
+    cx.capture_action(ConversationEditor::cancel_last_assist);
+    cx.capture_action(ConversationEditor::save);
+    cx.add_action(ConversationEditor::quote_selection);
+    cx.capture_action(ConversationEditor::copy);
+    cx.add_action(ConversationEditor::split);
+    cx.capture_action(ConversationEditor::cycle_message_role);
     cx.add_action(AssistantPanel::save_api_key);
     cx.add_action(AssistantPanel::reset_api_key);
+    cx.add_action(AssistantPanel::toggle_zoom);
+    cx.add_action(AssistantPanel::deploy);
+    cx.add_action(AssistantPanel::select_next_match);
+    cx.add_action(AssistantPanel::select_prev_match);
+    cx.add_action(AssistantPanel::handle_editor_cancel);
     cx.add_action(
         |workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
             workspace.toggle_panel_focus::<AssistantPanel>(cx);
@@ -83,6 +91,7 @@ pub fn init(cx: &mut AppContext) {
     );
 }
 
+#[derive(Debug)]
 pub enum AssistantPanelEvent {
     ZoomIn,
     ZoomOut,
@@ -92,15 +101,24 @@ pub enum AssistantPanelEvent {
 }
 
 pub struct AssistantPanel {
+    workspace: WeakViewHandle<Workspace>,
     width: Option<f32>,
     height: Option<f32>,
-    pane: ViewHandle<Pane>,
+    active_editor_index: Option<usize>,
+    prev_active_editor_index: Option<usize>,
+    editors: Vec<ViewHandle<ConversationEditor>>,
+    saved_conversations: Vec<SavedConversationMetadata>,
+    saved_conversations_list_state: UniformListState,
+    zoomed: bool,
+    has_focus: bool,
+    toolbar: ViewHandle<Toolbar>,
     api_key: Rc<RefCell<Option<String>>>,
     api_key_editor: Option<ViewHandle<Editor>>,
     has_read_credentials: bool,
     languages: Arc<LanguageRegistry>,
     fs: Arc<dyn Fs>,
     subscriptions: Vec<Subscription>,
+    _watch_saved_conversations: Task<Result<()>>,
 }
 
 impl AssistantPanel {
@@ -109,66 +127,51 @@ impl AssistantPanel {
         cx: AsyncAppContext,
     ) -> Task<Result<ViewHandle<Self>>> {
         cx.spawn(|mut cx| async move {
+            let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
+            let saved_conversations = SavedConversationMetadata::list(fs.clone())
+                .await
+                .log_err()
+                .unwrap_or_default();
+
             // TODO: deserialize state.
+            let workspace_handle = workspace.clone();
             workspace.update(&mut cx, |workspace, cx| {
                 cx.add_view::<Self, _>(|cx| {
-                    let weak_self = cx.weak_handle();
-                    let pane = cx.add_view(|cx| {
-                        let mut pane = Pane::new(
-                            workspace.weak_handle(),
-                            workspace.project().clone(),
-                            workspace.app_state().background_actions,
-                            Default::default(),
-                            cx,
-                        );
-                        pane.set_can_split(false, cx);
-                        pane.set_can_navigate(false, cx);
-                        pane.on_can_drop(move |_, _| false);
-                        pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
-                            let weak_self = weak_self.clone();
-                            Flex::row()
-                                .with_child(Pane::render_tab_bar_button(
-                                    0,
-                                    "icons/plus_12.svg",
-                                    false,
-                                    Some(("New Context".into(), Some(Box::new(NewContext)))),
-                                    cx,
-                                    move |_, cx| {
-                                        let weak_self = weak_self.clone();
-                                        cx.window_context().defer(move |cx| {
-                                            if let Some(this) = weak_self.upgrade(cx) {
-                                                this.update(cx, |this, cx| this.add_context(cx));
-                                            }
-                                        })
-                                    },
-                                    None,
-                                ))
-                                .with_child(Pane::render_tab_bar_button(
-                                    1,
-                                    if pane.is_zoomed() {
-                                        "icons/minimize_8.svg"
-                                    } else {
-                                        "icons/maximize_8.svg"
-                                    },
-                                    pane.is_zoomed(),
-                                    Some((
-                                        "Toggle Zoom".into(),
-                                        Some(Box::new(workspace::ToggleZoom)),
-                                    )),
-                                    cx,
-                                    move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
-                                    None,
-                                ))
-                                .into_any()
-                        });
-                        let buffer_search_bar = cx.add_view(search::BufferSearchBar::new);
-                        pane.toolbar()
-                            .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
-                        pane
+                    const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
+                    let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
+                        let mut events = fs
+                            .watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
+                            .await;
+                        while events.next().await.is_some() {
+                            let saved_conversations = SavedConversationMetadata::list(fs.clone())
+                                .await
+                                .log_err()
+                                .unwrap_or_default();
+                            this.update(&mut cx, |this, _| {
+                                this.saved_conversations = saved_conversations
+                            })
+                            .ok();
+                        }
+
+                        anyhow::Ok(())
                     });
 
+                    let toolbar = cx.add_view(|cx| {
+                        let mut toolbar = Toolbar::new(None);
+                        toolbar.set_can_navigate(false, cx);
+                        toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx);
+                        toolbar
+                    });
                     let mut this = Self {
-                        pane,
+                        workspace: workspace_handle,
+                        active_editor_index: Default::default(),
+                        prev_active_editor_index: Default::default(),
+                        editors: Default::default(),
+                        saved_conversations,
+                        saved_conversations_list_state: Default::default(),
+                        zoomed: false,
+                        has_focus: false,
+                        toolbar,
                         api_key: Rc::new(RefCell::new(None)),
                         api_key_editor: None,
                         has_read_credentials: false,
@@ -177,20 +180,18 @@ impl AssistantPanel {
                         width: None,
                         height: None,
                         subscriptions: Default::default(),
+                        _watch_saved_conversations,
                     };
 
                     let mut old_dock_position = this.position(cx);
-                    this.subscriptions = vec![
-                        cx.observe(&this.pane, |_, _, cx| cx.notify()),
-                        cx.subscribe(&this.pane, Self::handle_pane_event),
-                        cx.observe_global::<SettingsStore, _>(move |this, cx| {
+                    this.subscriptions =
+                        vec![cx.observe_global::<SettingsStore, _>(move |this, cx| {
                             let new_dock_position = this.position(cx);
                             if new_dock_position != old_dock_position {
                                 old_dock_position = new_dock_position;
                                 cx.emit(AssistantPanelEvent::DockPositionChanged);
                             }
-                        }),
-                    ];
+                        })];
 
                     this
                 })
@@ -198,40 +199,64 @@ impl AssistantPanel {
         })
     }
 
-    fn handle_pane_event(
+    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<ConversationEditor> {
+        let editor = cx.add_view(|cx| {
+            ConversationEditor::new(
+                self.api_key.clone(),
+                self.languages.clone(),
+                self.fs.clone(),
+                cx,
+            )
+        });
+        self.add_conversation(editor.clone(), cx);
+        editor
+    }
+
+    fn add_conversation(
         &mut self,
-        _pane: ViewHandle<Pane>,
-        event: &pane::Event,
+        editor: ViewHandle<ConversationEditor>,
         cx: &mut ViewContext<Self>,
     ) {
-        match event {
-            pane::Event::ZoomIn => cx.emit(AssistantPanelEvent::ZoomIn),
-            pane::Event::ZoomOut => cx.emit(AssistantPanelEvent::ZoomOut),
-            pane::Event::Focus => cx.emit(AssistantPanelEvent::Focus),
-            pane::Event::Remove => cx.emit(AssistantPanelEvent::Close),
-            _ => {}
-        }
-    }
+        self.subscriptions
+            .push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
 
-    fn add_context(&mut self, cx: &mut ViewContext<Self>) {
-        let focus = self.has_focus(cx);
-        let editor = cx
-            .add_view(|cx| AssistantEditor::new(self.api_key.clone(), self.languages.clone(), cx));
+        let conversation = editor.read(cx).conversation.clone();
         self.subscriptions
-            .push(cx.subscribe(&editor, Self::handle_assistant_editor_event));
-        self.pane.update(cx, |pane, cx| {
-            pane.add_item(Box::new(editor), true, focus, None, cx)
-        });
+            .push(cx.observe(&conversation, |_, _, cx| cx.notify()));
+
+        let index = self.editors.len();
+        self.editors.push(editor);
+        self.set_active_editor_index(Some(index), cx);
     }
 
-    fn handle_assistant_editor_event(
+    fn set_active_editor_index(&mut self, index: Option<usize>, cx: &mut ViewContext<Self>) {
+        self.prev_active_editor_index = self.active_editor_index;
+        self.active_editor_index = index;
+        if let Some(editor) = self.active_editor() {
+            let editor = editor.read(cx).editor.clone();
+            self.toolbar.update(cx, |toolbar, cx| {
+                toolbar.set_active_item(Some(&editor), cx);
+            });
+            if self.has_focus(cx) {
+                cx.focus(&editor);
+            }
+        } else {
+            self.toolbar.update(cx, |toolbar, cx| {
+                toolbar.set_active_item(None, cx);
+            });
+        }
+
+        cx.notify();
+    }
+
+    fn handle_conversation_editor_event(
         &mut self,
-        _: ViewHandle<AssistantEditor>,
-        event: &AssistantEditorEvent,
+        _: ViewHandle<ConversationEditor>,
+        event: &ConversationEditorEvent,
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            AssistantEditorEvent::TabContentChanged => self.pane.update(cx, |_, cx| cx.notify()),
+            ConversationEditorEvent::TabContentChanged => cx.notify(),
         }
     }
 
@@ -262,6 +287,287 @@ impl AssistantPanel {
         cx.focus_self();
         cx.notify();
     }
+
+    fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
+        if self.zoomed {
+            cx.emit(AssistantPanelEvent::ZoomOut)
+        } else {
+            cx.emit(AssistantPanelEvent::ZoomIn)
+        }
+    }
+
+    fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
+                return;
+            }
+        }
+        cx.propagate_action();
+    }
+
+    fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            if !search_bar.read(cx).is_dismissed() {
+                search_bar.update(cx, |search_bar, cx| {
+                    search_bar.dismiss(&Default::default(), cx)
+                });
+                return;
+            }
+        }
+        cx.propagate_action();
+    }
+
+    fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx));
+        }
+    }
+
+    fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx));
+        }
+    }
+
+    fn active_editor(&self) -> Option<&ViewHandle<ConversationEditor>> {
+        self.editors.get(self.active_editor_index?)
+    }
+
+    fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        enum History {}
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        MouseEventHandler::<History, _>::new(0, cx, |state, _| {
+            let style = theme.assistant.hamburger_button.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+            if this.active_editor().is_some() {
+                this.set_active_editor_index(None, cx);
+            } else {
+                this.set_active_editor_index(this.prev_active_editor_index, cx);
+            }
+        })
+        .with_tooltip::<History>(1, "History".into(), None, tooltip_style, cx)
+    }
+
+    fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement<Self>> {
+        if self.active_editor().is_some() {
+            vec![
+                Self::render_split_button(cx).into_any(),
+                Self::render_quote_button(cx).into_any(),
+                Self::render_assist_button(cx).into_any(),
+            ]
+        } else {
+            Default::default()
+        }
+    }
+
+    fn render_split_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        MouseEventHandler::<Split, _>::new(0, cx, |state, _| {
+            let style = theme.assistant.split_button.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+            if let Some(active_editor) = this.active_editor() {
+                active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
+            }
+        })
+        .with_tooltip::<Split>(
+            1,
+            "Split Message".into(),
+            Some(Box::new(Split)),
+            tooltip_style,
+            cx,
+        )
+    }
+
+    fn render_assist_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        MouseEventHandler::<Assist, _>::new(0, cx, |state, _| {
+            let style = theme.assistant.assist_button.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+            if let Some(active_editor) = this.active_editor() {
+                active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
+            }
+        })
+        .with_tooltip::<Assist>(
+            1,
+            "Assist".into(),
+            Some(Box::new(Assist)),
+            tooltip_style,
+            cx,
+        )
+    }
+
+    fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        MouseEventHandler::<QuoteSelection, _>::new(0, cx, |state, _| {
+            let style = theme.assistant.quote_button.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+            if let Some(workspace) = this.workspace.upgrade(cx) {
+                cx.window_context().defer(move |cx| {
+                    workspace.update(cx, |workspace, cx| {
+                        ConversationEditor::quote_selection(workspace, &Default::default(), cx)
+                    });
+                });
+            }
+        })
+        .with_tooltip::<QuoteSelection>(
+            1,
+            "Quote Selection".into(),
+            Some(Box::new(QuoteSelection)),
+            tooltip_style,
+            cx,
+        )
+    }
+
+    fn render_plus_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        MouseEventHandler::<NewConversation, _>::new(0, cx, |state, _| {
+            let style = theme.assistant.plus_button.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+            this.new_conversation(cx);
+        })
+        .with_tooltip::<NewConversation>(
+            1,
+            "New Conversation".into(),
+            Some(Box::new(NewConversation)),
+            tooltip_style,
+            cx,
+        )
+    }
+
+    fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
+        enum ToggleZoomButton {}
+
+        let theme = theme::current(cx);
+        let tooltip_style = theme::current(cx).tooltip.clone();
+        let style = if self.zoomed {
+            &theme.assistant.zoom_out_button
+        } else {
+            &theme.assistant.zoom_in_button
+        };
+
+        MouseEventHandler::<ToggleZoomButton, _>::new(0, cx, |state, _| {
+            let style = style.style_for(state);
+            Svg::for_style(style.icon.clone())
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, |_, this, cx| {
+            this.toggle_zoom(&ToggleZoom, cx);
+        })
+        .with_tooltip::<ToggleZoom>(
+            0,
+            if self.zoomed {
+                "Zoom Out".into()
+            } else {
+                "Zoom In".into()
+            },
+            Some(Box::new(ToggleZoom)),
+            tooltip_style,
+            cx,
+        )
+    }
+
+    fn render_saved_conversation(
+        &mut self,
+        index: usize,
+        cx: &mut ViewContext<Self>,
+    ) -> impl Element<Self> {
+        let conversation = &self.saved_conversations[index];
+        let path = conversation.path.clone();
+        MouseEventHandler::<SavedConversationMetadata, _>::new(index, cx, move |state, cx| {
+            let style = &theme::current(cx).assistant.saved_conversation;
+            Flex::row()
+                .with_child(
+                    Label::new(
+                        conversation.mtime.format("%F %I:%M%p").to_string(),
+                        style.saved_at.text.clone(),
+                    )
+                    .aligned()
+                    .contained()
+                    .with_style(style.saved_at.container),
+                )
+                .with_child(
+                    Label::new(conversation.title.clone(), style.title.text.clone())
+                        .aligned()
+                        .contained()
+                        .with_style(style.title.container),
+                )
+                .contained()
+                .with_style(*style.container.style_for(state))
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, this, cx| {
+            this.open_conversation(path.clone(), cx)
+                .detach_and_log_err(cx)
+        })
+    }
+
+    fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+        if let Some(ix) = self.editor_index_for_path(&path, cx) {
+            self.set_active_editor_index(Some(ix), cx);
+            return Task::ready(Ok(()));
+        }
+
+        let fs = self.fs.clone();
+        let api_key = self.api_key.clone();
+        let languages = self.languages.clone();
+        cx.spawn(|this, mut cx| async move {
+            let saved_conversation = fs.load(&path).await?;
+            let saved_conversation = serde_json::from_str(&saved_conversation)?;
+            let conversation = cx.add_model(|cx| {
+                Conversation::deserialize(saved_conversation, path.clone(), api_key, languages, cx)
+            });
+            this.update(&mut cx, |this, cx| {
+                // If, by the time we've loaded the conversation, the user has already opened
+                // the same conversation, we don't want to open it again.
+                if let Some(ix) = this.editor_index_for_path(&path, cx) {
+                    this.set_active_editor_index(Some(ix), cx);
+                } else {
+                    let editor = cx
+                        .add_view(|cx| ConversationEditor::for_conversation(conversation, fs, cx));
+                    this.add_conversation(editor, cx);
+                }
+            })?;
+            Ok(())
+        })
+    }
+
+    fn editor_index_for_path(&self, path: &Path, cx: &AppContext) -> Option<usize> {
+        self.editors
+            .iter()
+            .position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path))
+    }
 }
 
 fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> ViewHandle<Editor> {
@@ -285,7 +591,8 @@ impl View for AssistantPanel {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let style = &theme::current(cx).assistant;
+        let theme = &theme::current(cx);
+        let style = &theme.assistant;
         if let Some(api_key_editor) = self.api_key_editor.as_ref() {
             Flex::column()
                 .with_child(
@@ -306,19 +613,81 @@ impl View for AssistantPanel {
                 .aligned()
                 .into_any()
         } else {
-            ChildView::new(&self.pane, cx).into_any()
+            let title = self.active_editor().map(|editor| {
+                Label::new(editor.read(cx).title(cx), style.title.text.clone())
+                    .contained()
+                    .with_style(style.title.container)
+                    .aligned()
+                    .left()
+                    .flex(1., false)
+            });
+            let mut header = Flex::row()
+                .with_child(Self::render_hamburger_button(cx).aligned())
+                .with_children(title);
+            if self.has_focus {
+                header.add_children(
+                    self.render_editor_tools(cx)
+                        .into_iter()
+                        .map(|tool| tool.aligned().flex_float()),
+                );
+                header.add_child(Self::render_plus_button(cx).aligned().flex_float());
+                header.add_child(self.render_zoom_button(cx).aligned());
+            }
+
+            Flex::column()
+                .with_child(
+                    header
+                        .contained()
+                        .with_style(theme.workspace.tab_bar.container)
+                        .expanded()
+                        .constrained()
+                        .with_height(theme.workspace.tab_bar.height),
+                )
+                .with_children(if self.toolbar.read(cx).hidden() {
+                    None
+                } else {
+                    Some(ChildView::new(&self.toolbar, cx).expanded())
+                })
+                .with_child(if let Some(editor) = self.active_editor() {
+                    ChildView::new(editor, cx).flex(1., true).into_any()
+                } else {
+                    UniformList::new(
+                        self.saved_conversations_list_state.clone(),
+                        self.saved_conversations.len(),
+                        cx,
+                        |this, range, items, cx| {
+                            for ix in range {
+                                items.push(this.render_saved_conversation(ix, cx).into_any());
+                            }
+                        },
+                    )
+                    .flex(1., true)
+                    .into_any()
+                })
+                .into_any()
         }
     }
 
     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.has_focus = true;
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
+        cx.notify();
         if cx.is_self_focused() {
-            if let Some(api_key_editor) = self.api_key_editor.as_ref() {
+            if let Some(editor) = self.active_editor() {
+                cx.focus(editor);
+            } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
                 cx.focus(api_key_editor);
-            } else {
-                cx.focus(&self.pane);
             }
         }
     }
+
+    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.has_focus = false;
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
+        cx.notify();
+    }
 }
 
 impl Panel for AssistantPanel {
@@ -371,19 +740,22 @@ impl Panel for AssistantPanel {
         matches!(event, AssistantPanelEvent::ZoomOut)
     }
 
-    fn is_zoomed(&self, cx: &WindowContext) -> bool {
-        self.pane.read(cx).is_zoomed()
+    fn is_zoomed(&self, _: &WindowContext) -> bool {
+        self.zoomed
     }
 
     fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
+        self.zoomed = zoomed;
+        cx.notify();
     }
 
     fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
         if active {
             if self.api_key.borrow().is_none() && !self.has_read_credentials {
                 self.has_read_credentials = true;
-                let api_key = if let Some((_, api_key)) = cx
+                let api_key = if let Ok(api_key) = env::var("OPENAI_API_KEY") {
+                    Some(api_key)
+                } else if let Some((_, api_key)) = cx
                     .platform()
                     .read_credentials(OPENAI_API_URL)
                     .log_err()
@@ -401,8 +773,8 @@ impl Panel for AssistantPanel {
                 }
             }
 
-            if self.pane.read(cx).items_len() == 0 {
-                self.add_context(cx);
+            if self.editors.is_empty() {
+                self.new_conversation(cx);
             }
         }
     }
@@ -427,12 +799,8 @@ impl Panel for AssistantPanel {
         matches!(event, AssistantPanelEvent::Close)
     }
 
-    fn has_focus(&self, cx: &WindowContext) -> bool {
-        self.pane.read(cx).has_focus()
-            || self
-                .api_key_editor
-                .as_ref()
-                .map_or(false, |editor| editor.is_focused(cx))
+    fn has_focus(&self, _: &WindowContext) -> bool {
+        self.has_focus
     }
 
     fn is_focus_event(event: &Self::Event) -> bool {
@@ -440,18 +808,24 @@ impl Panel for AssistantPanel {
     }
 }
 
-enum AssistantEvent {
+enum ConversationEvent {
     MessagesEdited,
     SummaryChanged,
     StreamedCompletion,
 }
 
-struct Assistant {
+#[derive(Default)]
+struct Summary {
+    text: String,
+    done: bool,
+}
+
+struct Conversation {
     buffer: ModelHandle<Buffer>,
     message_anchors: Vec<MessageAnchor>,
     messages_metadata: HashMap<MessageId, MessageMetadata>,
     next_message_id: MessageId,
-    summary: Option<String>,
+    summary: Option<Summary>,
     pending_summary: Task<Option<()>>,
     completion_count: usize,
     pending_completions: Vec<PendingCompletion>,
@@ -460,14 +834,16 @@ struct Assistant {
     max_token_count: usize,
     pending_token_count: Task<Option<()>>,
     api_key: Rc<RefCell<Option<String>>>,
+    pending_save: Task<Result<()>>,
+    path: Option<PathBuf>,
     _subscriptions: Vec<Subscription>,
 }
 
-impl Entity for Assistant {
-    type Event = AssistantEvent;
+impl Entity for Conversation {
+    type Event = ConversationEvent;
 }
 
-impl Assistant {
+impl Conversation {
     fn new(
         api_key: Rc<RefCell<Option<String>>>,
         language_registry: Arc<LanguageRegistry>,
@@ -505,6 +881,8 @@ impl Assistant {
             pending_token_count: Task::ready(None),
             model: model.into(),
             _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
+            pending_save: Task::ready(Ok(())),
+            path: None,
             api_key,
             buffer,
         };
@@ -526,6 +904,88 @@ impl Assistant {
         this
     }
 
+    fn serialize(&self, cx: &AppContext) -> SavedConversation {
+        SavedConversation {
+            zed: "conversation".into(),
+            version: SavedConversation::VERSION.into(),
+            text: self.buffer.read(cx).text(),
+            message_metadata: self.messages_metadata.clone(),
+            messages: self
+                .messages(cx)
+                .map(|message| SavedMessage {
+                    id: message.id,
+                    start: message.offset_range.start,
+                })
+                .collect(),
+            summary: self
+                .summary
+                .as_ref()
+                .map(|summary| summary.text.clone())
+                .unwrap_or_default(),
+            model: self.model.clone(),
+        }
+    }
+
+    fn deserialize(
+        saved_conversation: SavedConversation,
+        path: PathBuf,
+        api_key: Rc<RefCell<Option<String>>>,
+        language_registry: Arc<LanguageRegistry>,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        let model = saved_conversation.model;
+        let markdown = language_registry.language_for_name("Markdown");
+        let mut message_anchors = Vec::new();
+        let mut next_message_id = MessageId(0);
+        let buffer = cx.add_model(|cx| {
+            let mut buffer = Buffer::new(0, saved_conversation.text, cx);
+            for message in saved_conversation.messages {
+                message_anchors.push(MessageAnchor {
+                    id: message.id,
+                    start: buffer.anchor_before(message.start),
+                });
+                next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
+            }
+            buffer.set_language_registry(language_registry);
+            cx.spawn_weak(|buffer, mut cx| async move {
+                let markdown = markdown.await?;
+                let buffer = buffer
+                    .upgrade(&cx)
+                    .ok_or_else(|| anyhow!("buffer was dropped"))?;
+                buffer.update(&mut cx, |buffer, cx| {
+                    buffer.set_language(Some(markdown), cx)
+                });
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
+            buffer
+        });
+
+        let mut this = Self {
+            message_anchors,
+            messages_metadata: saved_conversation.message_metadata,
+            next_message_id,
+            summary: Some(Summary {
+                text: saved_conversation.summary,
+                done: true,
+            }),
+            pending_summary: Task::ready(None),
+            completion_count: Default::default(),
+            pending_completions: Default::default(),
+            token_count: None,
+            max_token_count: tiktoken_rs::model::get_context_size(&model),
+            pending_token_count: Task::ready(None),
+            model,
+            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
+            pending_save: Task::ready(Ok(())),
+            path: Some(path),
+            api_key,
+            buffer,
+        };
+        this.count_remaining_tokens(cx);
+        this
+    }
+
     fn handle_buffer_event(
         &mut self,
         _: ModelHandle<Buffer>,
@@ -535,7 +995,7 @@ impl Assistant {
         match event {
             language::Event::Edited => {
                 self.count_remaining_tokens(cx);
-                cx.emit(AssistantEvent::MessagesEdited);
+                cx.emit(ConversationEvent::MessagesEdited);
             }
             _ => {}
         }
@@ -552,7 +1012,11 @@ impl Assistant {
                         Role::Assistant => "assistant".into(),
                         Role::System => "system".into(),
                     },
-                    content: self.buffer.read(cx).text_for_range(message.range).collect(),
+                    content: self
+                        .buffer
+                        .read(cx)
+                        .text_for_range(message.offset_range)
+                        .collect(),
                     name: None,
                 })
             })
@@ -567,7 +1031,7 @@ impl Assistant {
                     .await?;
 
                 this.upgrade(&cx)
-                    .ok_or_else(|| anyhow!("assistant was dropped"))?
+                    .ok_or_else(|| anyhow!("conversation was dropped"))?
                     .update(&mut cx, |this, cx| {
                         this.max_token_count = tiktoken_rs::model::get_context_size(&this.model);
                         this.token_count = Some(token_count);
@@ -596,6 +1060,14 @@ impl Assistant {
     ) -> Vec<MessageAnchor> {
         let mut user_messages = Vec::new();
         let mut tasks = Vec::new();
+
+        let last_message_id = self.message_anchors.iter().rev().find_map(|message| {
+            message
+                .start
+                .is_valid(self.buffer.read(cx))
+                .then_some(message.id)
+        });
+
         for selected_message_id in selected_messages {
             let selected_message_role =
                 if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
@@ -658,6 +1130,19 @@ impl Assistant {
                     )
                     .unwrap();
 
+                // Queue up the user's next reply
+                if Some(selected_message_id) == last_message_id {
+                    let user_message = self
+                        .insert_message_after(
+                            assistant_message.id,
+                            Role::User,
+                            MessageStatus::Done,
+                            cx,
+                        )
+                        .unwrap();
+                    user_messages.push(user_message);
+                }
+
                 tasks.push(cx.spawn_weak({
                     |this, mut cx| async move {
                         let assistant_message_id = assistant_message.id;

crates/auto_update/src/update_notification.rs πŸ”—

@@ -49,7 +49,7 @@ impl View for UpdateNotification {
                         )
                         .with_child(
                             MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
-                                let style = theme.dismiss_button.style_for(state, false);
+                                let style = theme.dismiss_button.style_for(state);
                                 Svg::new("icons/x_mark_8.svg")
                                     .with_color(style.color)
                                     .constrained()
@@ -74,7 +74,7 @@ impl View for UpdateNotification {
                         ),
                 )
                 .with_child({
-                    let style = theme.action_message.style_for(state, false);
+                    let style = theme.action_message.style_for(state);
                     Text::new("View the release notes", style.text.clone())
                         .contained()
                         .with_style(style.container)

crates/breadcrumbs/src/breadcrumbs.rs πŸ”—

@@ -83,7 +83,7 @@ impl View for Breadcrumbs {
         }
 
         MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
-            let style = style.style_for(state, false);
+            let style = style.style_for(state);
             crumbs.with_style(style.container)
         })
         .on_click(MouseButton::Left, |_, this, cx| {

crates/call/src/participant.rs πŸ”—

@@ -3,6 +3,7 @@ use client::{proto, User};
 use collections::HashMap;
 use gpui::WeakModelHandle;
 pub use live_kit_client::Frame;
+use live_kit_client::RemoteAudioTrack;
 use project::Project;
 use std::{fmt, sync::Arc};
 
@@ -42,7 +43,10 @@ pub struct RemoteParticipant {
     pub peer_id: proto::PeerId,
     pub projects: Vec<proto::ParticipantProject>,
     pub location: ParticipantLocation,
-    pub tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
+    pub muted: bool,
+    pub speaking: bool,
+    pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
+    pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
 }
 
 #[derive(Clone)]

crates/call/src/room.rs πŸ”—

@@ -12,7 +12,10 @@ use fs::Fs;
 use futures::{FutureExt, StreamExt};
 use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
 use language::LanguageRegistry;
-use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate};
+use live_kit_client::{
+    LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
+    RemoteVideoTrackUpdate,
+};
 use postage::stream::Stream;
 use project::Project;
 use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
@@ -28,6 +31,9 @@ pub enum Event {
     RemoteVideoTracksChanged {
         participant_id: proto::PeerId,
     },
+    RemoteAudioTracksChanged {
+        participant_id: proto::PeerId,
+    },
     RemoteProjectShared {
         owner: Arc<User>,
         project_id: u64,
@@ -112,9 +118,9 @@ impl Room {
                 }
             });
 
-            let mut track_changes = room.remote_video_track_updates();
-            let _maintain_tracks = cx.spawn_weak(|this, mut cx| async move {
-                while let Some(track_change) = track_changes.next().await {
+            let mut track_video_changes = room.remote_video_track_updates();
+            let _maintain_video_tracks = cx.spawn_weak(|this, mut cx| async move {
+                while let Some(track_change) = track_video_changes.next().await {
                     let this = if let Some(this) = this.upgrade(&cx) {
                         this
                     } else {
@@ -127,16 +133,41 @@ impl Room {
                 }
             });
 
-            cx.foreground()
-                .spawn(room.connect(&connection_info.server_url, &connection_info.token))
-                .detach_and_log_err(cx);
+            let mut track_audio_changes = room.remote_audio_track_updates();
+            let _maintain_audio_tracks = cx.spawn_weak(|this, mut cx| async move {
+                while let Some(track_change) = track_audio_changes.next().await {
+                    let this = if let Some(this) = this.upgrade(&cx) {
+                        this
+                    } else {
+                        break;
+                    };
+
+                    this.update(&mut cx, |this, cx| {
+                        this.remote_audio_track_updated(track_change, cx).log_err()
+                    });
+                }
+            });
+
+            let connect = room.connect(&connection_info.server_url, &connection_info.token);
+            cx.spawn(|this, mut cx| async move {
+                connect.await?;
+                this.update(&mut cx, |this, cx| this.share_microphone(cx))
+                    .await?;
+
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
 
             Some(LiveKitRoom {
                 room,
-                screen_track: ScreenTrack::None,
+                screen_track: LocalTrack::None,
+                microphone_track: LocalTrack::None,
                 next_publish_id: 0,
+                muted_by_user: false,
+                deafened: false,
+                speaking: false,
                 _maintain_room,
-                _maintain_tracks,
+                _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks],
             })
         } else {
             None
@@ -618,20 +649,32 @@ impl Room {
                                     peer_id,
                                     projects: participant.projects,
                                     location,
-                                    tracks: Default::default(),
+                                    muted: false,
+                                    speaking: false,
+                                    video_tracks: Default::default(),
+                                    audio_tracks: Default::default(),
                                 },
                             );
 
                             if let Some(live_kit) = this.live_kit.as_ref() {
-                                let tracks =
+                                let video_tracks =
                                     live_kit.room.remote_video_tracks(&user.id.to_string());
-                                for track in tracks {
+                                let audio_tracks =
+                                    live_kit.room.remote_audio_tracks(&user.id.to_string());
+                                for track in video_tracks {
                                     this.remote_video_track_updated(
                                         RemoteVideoTrackUpdate::Subscribed(track),
                                         cx,
                                     )
                                     .log_err();
                                 }
+                                for track in audio_tracks {
+                                    this.remote_audio_track_updated(
+                                        RemoteAudioTrackUpdate::Subscribed(track),
+                                        cx,
+                                    )
+                                    .log_err();
+                                }
                             }
                         }
                     }
@@ -706,7 +749,7 @@ impl Room {
                     .remote_participants
                     .get_mut(&user_id)
                     .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
-                participant.tracks.insert(
+                participant.video_tracks.insert(
                     track_id.clone(),
                     Arc::new(RemoteVideoTrack {
                         live_kit_track: track,
@@ -725,7 +768,7 @@ impl Room {
                     .remote_participants
                     .get_mut(&user_id)
                     .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
-                participant.tracks.remove(&track_id);
+                participant.video_tracks.remove(&track_id);
                 cx.emit(Event::RemoteVideoTracksChanged {
                     participant_id: participant.peer_id,
                 });
@@ -736,6 +779,84 @@ impl Room {
         Ok(())
     }
 
+    fn remote_audio_track_updated(
+        &mut self,
+        change: RemoteAudioTrackUpdate,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        match change {
+            RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } => {
+                let mut speaker_ids = speakers
+                    .into_iter()
+                    .filter_map(|speaker_sid| speaker_sid.parse().ok())
+                    .collect::<Vec<u64>>();
+                speaker_ids.sort_unstable();
+                for (sid, participant) in &mut self.remote_participants {
+                    if let Ok(_) = speaker_ids.binary_search(sid) {
+                        participant.speaking = true;
+                    } else {
+                        participant.speaking = false;
+                    }
+                }
+                if let Some(id) = self.client.user_id() {
+                    if let Some(room) = &mut self.live_kit {
+                        if let Ok(_) = speaker_ids.binary_search(&id) {
+                            room.speaking = true;
+                        } else {
+                            room.speaking = false;
+                        }
+                    }
+                }
+                cx.notify();
+            }
+            RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
+                for participant in &mut self.remote_participants.values_mut() {
+                    let mut found = false;
+                    for track in participant.audio_tracks.values() {
+                        if track.sid() == track_id {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if found {
+                        participant.muted = muted;
+                        break;
+                    }
+                }
+                cx.notify();
+            }
+            RemoteAudioTrackUpdate::Subscribed(track) => {
+                let user_id = track.publisher_id().parse()?;
+                let track_id = track.sid().to_string();
+                let participant = self
+                    .remote_participants
+                    .get_mut(&user_id)
+                    .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
+                participant.audio_tracks.insert(track_id.clone(), track);
+                cx.emit(Event::RemoteAudioTracksChanged {
+                    participant_id: participant.peer_id,
+                });
+            }
+            RemoteAudioTrackUpdate::Unsubscribed {
+                publisher_id,
+                track_id,
+            } => {
+                let user_id = publisher_id.parse()?;
+                let participant = self
+                    .remote_participants
+                    .get_mut(&user_id)
+                    .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
+                participant.audio_tracks.remove(&track_id);
+                cx.emit(Event::RemoteAudioTracksChanged {
+                    participant_id: participant.peer_id,
+                });
+            }
+        }
+
+        cx.notify();
+        Ok(())
+    }
+
     fn check_invariants(&self) {
         #[cfg(any(test, feature = "test-support"))]
         {
@@ -908,7 +1029,116 @@ impl Room {
 
     pub fn is_screen_sharing(&self) -> bool {
         self.live_kit.as_ref().map_or(false, |live_kit| {
-            !matches!(live_kit.screen_track, ScreenTrack::None)
+            !matches!(live_kit.screen_track, LocalTrack::None)
+        })
+    }
+
+    pub fn is_sharing_mic(&self) -> bool {
+        self.live_kit.as_ref().map_or(false, |live_kit| {
+            !matches!(live_kit.microphone_track, LocalTrack::None)
+        })
+    }
+
+    pub fn is_muted(&self) -> bool {
+        self.live_kit
+            .as_ref()
+            .and_then(|live_kit| match &live_kit.microphone_track {
+                LocalTrack::None => None,
+                LocalTrack::Pending { muted, .. } => Some(*muted),
+                LocalTrack::Published { muted, .. } => Some(*muted),
+            })
+            .unwrap_or(false)
+    }
+
+    pub fn is_speaking(&self) -> bool {
+        self.live_kit
+            .as_ref()
+            .map_or(false, |live_kit| live_kit.speaking)
+    }
+
+    pub fn is_deafened(&self) -> Option<bool> {
+        self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
+    }
+
+    pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        if self.status.is_offline() {
+            return Task::ready(Err(anyhow!("room is offline")));
+        } else if self.is_sharing_mic() {
+            return Task::ready(Err(anyhow!("microphone was already shared")));
+        }
+
+        let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
+            let publish_id = post_inc(&mut live_kit.next_publish_id);
+            live_kit.microphone_track = LocalTrack::Pending {
+                publish_id,
+                muted: false,
+            };
+            cx.notify();
+            publish_id
+        } else {
+            return Task::ready(Err(anyhow!("live-kit was not initialized")));
+        };
+
+        cx.spawn_weak(|this, mut cx| async move {
+            let publish_track = async {
+                let track = LocalAudioTrack::create();
+                this.upgrade(&cx)
+                    .ok_or_else(|| anyhow!("room was dropped"))?
+                    .read_with(&cx, |this, _| {
+                        this.live_kit
+                            .as_ref()
+                            .map(|live_kit| live_kit.room.publish_audio_track(&track))
+                    })
+                    .ok_or_else(|| anyhow!("live-kit was not initialized"))?
+                    .await
+            };
+
+            let publication = publish_track.await;
+            this.upgrade(&cx)
+                .ok_or_else(|| anyhow!("room was dropped"))?
+                .update(&mut cx, |this, cx| {
+                    let live_kit = this
+                        .live_kit
+                        .as_mut()
+                        .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
+
+                    let (canceled, muted) = if let LocalTrack::Pending {
+                        publish_id: cur_publish_id,
+                        muted,
+                    } = &live_kit.microphone_track
+                    {
+                        (*cur_publish_id != publish_id, *muted)
+                    } else {
+                        (true, false)
+                    };
+
+                    match publication {
+                        Ok(publication) => {
+                            if canceled {
+                                live_kit.room.unpublish_track(publication);
+                            } else {
+                                if muted {
+                                    cx.background().spawn(publication.set_mute(muted)).detach();
+                                }
+                                live_kit.microphone_track = LocalTrack::Published {
+                                    track_publication: publication,
+                                    muted,
+                                };
+                                cx.notify();
+                            }
+                            Ok(())
+                        }
+                        Err(error) => {
+                            if canceled {
+                                Ok(())
+                            } else {
+                                live_kit.microphone_track = LocalTrack::None;
+                                cx.notify();
+                                Err(error)
+                            }
+                        }
+                    }
+                })
         })
     }
 
@@ -921,7 +1151,10 @@ impl Room {
 
         let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
             let publish_id = post_inc(&mut live_kit.next_publish_id);
-            live_kit.screen_track = ScreenTrack::Pending { publish_id };
+            live_kit.screen_track = LocalTrack::Pending {
+                publish_id,
+                muted: false,
+            };
             cx.notify();
             (live_kit.room.display_sources(), publish_id)
         } else {
@@ -955,13 +1188,14 @@ impl Room {
                         .as_mut()
                         .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
 
-                    let canceled = if let ScreenTrack::Pending {
+                    let (canceled, muted) = if let LocalTrack::Pending {
                         publish_id: cur_publish_id,
+                        muted,
                     } = &live_kit.screen_track
                     {
-                        *cur_publish_id != publish_id
+                        (*cur_publish_id != publish_id, *muted)
                     } else {
-                        true
+                        (true, false)
                     };
 
                     match publication {
@@ -969,7 +1203,13 @@ impl Room {
                             if canceled {
                                 live_kit.room.unpublish_track(publication);
                             } else {
-                                live_kit.screen_track = ScreenTrack::Published(publication);
+                                if muted {
+                                    cx.background().spawn(publication.set_mute(muted)).detach();
+                                }
+                                live_kit.screen_track = LocalTrack::Published {
+                                    track_publication: publication,
+                                    muted,
+                                };
                                 cx.notify();
                             }
                             Ok(())
@@ -978,7 +1218,7 @@ impl Room {
                             if canceled {
                                 Ok(())
                             } else {
-                                live_kit.screen_track = ScreenTrack::None;
+                                live_kit.screen_track = LocalTrack::None;
                                 cx.notify();
                                 Err(error)
                             }
@@ -987,6 +1227,77 @@ impl Room {
                 })
         })
     }
+    fn set_mute(
+        live_kit: &mut LiveKitRoom,
+        should_mute: bool,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<Task<Result<()>>> {
+        if !should_mute {
+            // clear user muting state.
+            live_kit.muted_by_user = false;
+        }
+        match &mut live_kit.microphone_track {
+            LocalTrack::None => Err(anyhow!("microphone was not shared")),
+            LocalTrack::Pending { muted, .. } => {
+                *muted = should_mute;
+                cx.notify();
+                Ok(Task::Ready(Some(Ok(()))))
+            }
+            LocalTrack::Published {
+                track_publication,
+                muted,
+            } => {
+                *muted = should_mute;
+                cx.notify();
+                Ok(cx.background().spawn(track_publication.set_mute(*muted)))
+            }
+        }
+    }
+    pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
+        let should_mute = !self.is_muted();
+        if let Some(live_kit) = self.live_kit.as_mut() {
+            let ret = Self::set_mute(live_kit, should_mute, cx);
+            live_kit.muted_by_user = should_mute;
+            ret
+        } else {
+            Err(anyhow!("LiveKit not started"))
+        }
+    }
+
+    pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
+        if let Some(live_kit) = self.live_kit.as_mut() {
+            (*live_kit).deafened = !live_kit.deafened;
+
+            let mut tasks = Vec::with_capacity(self.remote_participants.len());
+            // Context notification is sent within set_mute itself.
+            let mut mute_task = None;
+            // When deafening, mute user's mic as well.
+            // When undeafening, unmute user's mic unless it was manually muted prior to deafening.
+            if live_kit.deafened || !live_kit.muted_by_user {
+                mute_task = Some(Self::set_mute(live_kit, live_kit.deafened, cx)?);
+            };
+            for participant in self.remote_participants.values() {
+                for track in live_kit
+                    .room
+                    .remote_audio_track_publications(&participant.user.id.to_string())
+                {
+                    tasks.push(cx.foreground().spawn(track.set_enabled(!live_kit.deafened)));
+                }
+            }
+
+            Ok(cx.foreground().spawn(async move {
+                if let Some(mute_task) = mute_task {
+                    mute_task.await?;
+                }
+                for task in tasks {
+                    task.await?;
+                }
+                Ok(())
+            }))
+        } else {
+            Err(anyhow!("LiveKit not started"))
+        }
+    }
 
     pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
         if self.status.is_offline() {
@@ -998,13 +1309,15 @@ impl Room {
             .as_mut()
             .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
         match mem::take(&mut live_kit.screen_track) {
-            ScreenTrack::None => Err(anyhow!("screen was not shared")),
-            ScreenTrack::Pending { .. } => {
+            LocalTrack::None => Err(anyhow!("screen was not shared")),
+            LocalTrack::Pending { .. } => {
                 cx.notify();
                 Ok(())
             }
-            ScreenTrack::Published(track) => {
-                live_kit.room.unpublish_track(track);
+            LocalTrack::Published {
+                track_publication, ..
+            } => {
+                live_kit.room.unpublish_track(track_publication);
                 cx.notify();
                 Ok(())
             }
@@ -1023,19 +1336,30 @@ impl Room {
 
 struct LiveKitRoom {
     room: Arc<live_kit_client::Room>,
-    screen_track: ScreenTrack,
+    screen_track: LocalTrack,
+    microphone_track: LocalTrack,
+    /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user.
+    muted_by_user: bool,
+    deafened: bool,
+    speaking: bool,
     next_publish_id: usize,
     _maintain_room: Task<()>,
-    _maintain_tracks: Task<()>,
+    _maintain_tracks: [Task<()>; 2],
 }
 
-enum ScreenTrack {
+enum LocalTrack {
     None,
-    Pending { publish_id: usize },
-    Published(LocalTrackPublication),
+    Pending {
+        publish_id: usize,
+        muted: bool,
+    },
+    Published {
+        track_publication: LocalTrackPublication,
+        muted: bool,
+    },
 }
 
-impl Default for ScreenTrack {
+impl Default for LocalTrack {
     fn default() -> Self {
         Self::None
     }

crates/client/src/telemetry.rs πŸ”—

@@ -1,5 +1,4 @@
 use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
-use db::kvp::KEY_VALUE_STORE;
 use gpui::{executor::Background, serde_json, AppContext, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
@@ -8,7 +7,6 @@ use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
 use tempfile::NamedTempFile;
 use util::http::HttpClient;
 use util::{channel::ReleaseChannel, TryFutureExt};
-use uuid::Uuid;
 
 pub struct Telemetry {
     http_client: Arc<dyn HttpClient>,
@@ -120,39 +118,15 @@ impl Telemetry {
         Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
     }
 
-    pub fn start(self: &Arc<Self>) {
-        let this = self.clone();
-        self.executor
-            .spawn(
-                async move {
-                    let installation_id =
-                        if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp("device_id") {
-                            installation_id
-                        } else {
-                            let installation_id = Uuid::new_v4().to_string();
-                            KEY_VALUE_STORE
-                                .write_kvp("device_id".to_string(), installation_id.clone())
-                                .await?;
-                            installation_id
-                        };
-
-                    let installation_id: Arc<str> = installation_id.into();
-                    let mut state = this.state.lock();
-                    state.installation_id = Some(installation_id.clone());
-
-                    let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
-
-                    drop(state);
-
-                    if has_clickhouse_events {
-                        this.flush_clickhouse_events();
-                    }
+    pub fn start(self: &Arc<Self>, installation_id: Option<String>) {
+        let mut state = self.state.lock();
+        state.installation_id = installation_id.map(|id| id.into());
+        let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
+        drop(state);
 
-                    anyhow::Ok(())
-                }
-                .log_err(),
-            )
-            .detach();
+        if has_clickhouse_events {
+            self.flush_clickhouse_events();
+        }
     }
 
     /// This method takes the entire TelemetrySettings struct in order to force client code

crates/collab/Cargo.toml πŸ”—

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
 default-run = "collab"
 edition = "2021"
 name = "collab"
-version = "0.14.2"
+version = "0.15.0"
 publish = false
 
 [[bin]]

crates/collab/src/db.rs πŸ”—

@@ -1539,6 +1539,7 @@ impl Database {
                                     }),
                                     is_symlink: db_entry.is_symlink,
                                     is_ignored: db_entry.is_ignored,
+                                    is_external: db_entry.is_external,
                                     git_status: db_entry.git_status.map(|status| status as i32),
                                 });
                             }
@@ -2349,6 +2350,7 @@ impl Database {
                         mtime_nanos: ActiveValue::set(mtime.nanos as i32),
                         is_symlink: ActiveValue::set(entry.is_symlink),
                         is_ignored: ActiveValue::set(entry.is_ignored),
+                        is_external: ActiveValue::set(entry.is_external),
                         git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
                         is_deleted: ActiveValue::set(false),
                         scan_id: ActiveValue::set(update.scan_id as i64),
@@ -2705,6 +2707,7 @@ impl Database {
                             }),
                             is_symlink: db_entry.is_symlink,
                             is_ignored: db_entry.is_ignored,
+                            is_external: db_entry.is_external,
                             git_status: db_entry.git_status.map(|status| status as i32),
                         });
                     }

crates/collab/src/rpc.rs πŸ”—

@@ -224,6 +224,7 @@ impl Server {
             .add_request_handler(forward_project_request::<proto::RenameProjectEntry>)
             .add_request_handler(forward_project_request::<proto::CopyProjectEntry>)
             .add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
+            .add_request_handler(forward_project_request::<proto::ExpandProjectEntry>)
             .add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
             .add_message_handler(create_buffer_for_peer)
             .add_request_handler(update_buffer)

crates/collab/src/tests/integration_tests.rs πŸ”—

@@ -257,7 +257,7 @@ async fn test_basic_calls(
         room_b.read_with(cx_b, |room, _| {
             assert_eq!(
                 room.remote_participants()[&client_a.user_id().unwrap()]
-                    .tracks
+                    .video_tracks
                     .len(),
                 1
             );
@@ -274,7 +274,7 @@ async fn test_basic_calls(
         room_c.read_with(cx_c, |room, _| {
             assert_eq!(
                 room.remote_participants()[&client_a.user_id().unwrap()]
-                    .tracks
+                    .video_tracks
                     .len(),
                 1
             );
@@ -1266,6 +1266,27 @@ async fn test_share_project(
         let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
         assert_eq!(client_b_collaborator.replica_id, replica_id_b);
     });
+    project_b.read_with(cx_b, |project, cx| {
+        let worktree = project.worktrees(cx).next().unwrap().read(cx);
+        assert_eq!(
+            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
+            [
+                Path::new(".gitignore"),
+                Path::new("a.txt"),
+                Path::new("b.txt"),
+                Path::new("ignored-dir"),
+            ]
+        );
+    });
+
+    project_b
+        .update(cx_b, |project, cx| {
+            let worktree = project.worktrees(cx).next().unwrap();
+            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
+            project.expand_entry(worktree_id, entry.id, cx).unwrap()
+        })
+        .await
+        .unwrap();
     project_b.read_with(cx_b, |project, cx| {
         let worktree = project.worktrees(cx).next().unwrap().read(cx);
         assert_eq!(
@@ -6993,7 +7014,7 @@ async fn test_join_call_after_screen_was_shared(
             room.remote_participants()
                 .get(&client_a.user_id().unwrap())
                 .unwrap()
-                .tracks
+                .video_tracks
                 .len(),
             1
         );

crates/collab_ui/Cargo.toml πŸ”—

@@ -37,8 +37,10 @@ picker = { path = "../picker" }
 project = { path = "../project" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
+theme_selector = { path = "../theme_selector" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
+zed-actions = {path = "../zed-actions"}
 
 anyhow.workspace = true
 futures.workspace = true

crates/collab_ui/src/collab_titlebar_item.rs πŸ”—

@@ -1,6 +1,7 @@
 use crate::{
     contact_notification::ContactNotification, contacts_popover, face_pile::FacePile,
-    toggle_screen_sharing, ToggleScreenSharing,
+    toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute,
+    ToggleScreenSharing,
 };
 use call::{ActiveCall, ParticipantLocation, Room};
 use client::{proto::PeerId, Client, ContactEventKind, SignIn, SignOut, User, UserStore};
@@ -17,13 +18,13 @@ use gpui::{
     AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
     ViewContext, ViewHandle, WeakViewHandle,
 };
-use project::Project;
+use project::{Project, RepositoryEntry};
 use std::{ops::Range, sync::Arc};
 use theme::{AvatarStyle, Theme};
 use util::ResultExt;
 use workspace::{FollowNextCollaborator, Workspace};
 
-const MAX_TITLE_LENGTH: usize = 75;
+// const MAX_TITLE_LENGTH: usize = 75;
 
 actions!(
     collab,
@@ -78,27 +79,34 @@ impl View for CollabTitlebarItem {
         let user = self.user_store.read(cx).current_user();
         let peer_id = self.client.peer_id();
         if let Some(((user, peer_id), room)) = user
+            .as_ref()
             .zip(peer_id)
             .zip(ActiveCall::global(cx).read(cx).room().cloned())
         {
-            left_container
-                .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
-
-            right_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
             right_container
-                .add_child(self.render_current_user(&workspace, &theme, &user, peer_id, cx));
+                .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
+            right_container.add_child(self.render_leave_call(&theme, cx));
+            let muted = room.read(cx).is_muted();
+            let speaking = room.read(cx).is_speaking();
+            left_container.add_child(
+                self.render_current_user(&workspace, &theme, &user, peer_id, muted, speaking, cx),
+            );
+            left_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
+            right_container.add_child(self.render_toggle_mute(&theme, &room, cx));
+            right_container.add_child(self.render_toggle_deafen(&theme, &room, cx));
             right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
         }
 
         let status = workspace.read(cx).client().status();
         let status = &*status.borrow();
-
         if matches!(status, client::Status::Connected { .. }) {
             right_container.add_child(self.render_toggle_contacts_button(&theme, cx));
-            right_container.add_child(self.render_user_menu_button(&theme, cx));
+            let avatar = user.as_ref().and_then(|user| user.avatar.clone());
+            right_container.add_child(self.render_user_menu_button(&theme, avatar, cx));
         } else {
             right_container.add_children(self.render_connection_status(status, cx));
             right_container.add_child(self.render_sign_in_button(&theme, cx));
+            right_container.add_child(self.render_user_menu_button(&theme, None, cx));
         }
 
         Stack::new()
@@ -108,7 +116,6 @@ impl View for CollabTitlebarItem {
                     .with_child(
                         right_container.contained().with_background_color(
                             theme
-                                .workspace
                                 .titlebar
                                 .container
                                 .background_color
@@ -163,7 +170,6 @@ impl CollabTitlebarItem {
             }),
         );
 
-        let view_id = cx.view_id();
         Self {
             workspace: workspace.weak_handle(),
             project,
@@ -171,6 +177,7 @@ impl CollabTitlebarItem {
             client,
             contacts_popover: None,
             user_menu: cx.add_view(|cx| {
+                let view_id = cx.view_id();
                 let mut menu = ContextMenu::new(view_id, cx);
                 menu.set_position_mode(OverlayPositionMode::Local);
                 menu
@@ -185,55 +192,45 @@ impl CollabTitlebarItem {
         theme: Arc<Theme>,
         cx: &ViewContext<Self>,
     ) -> AnyElement<Self> {
-        let names_and_branches = project.visible_worktrees(cx).map(|worktree| {
+        let mut names_and_branches = project.visible_worktrees(cx).map(|worktree| {
             let worktree = worktree.read(cx);
             (worktree.root_name(), worktree.root_git_entry())
         });
 
-        fn push_str(buffer: &mut String, index: &mut usize, str: &str) {
-            buffer.push_str(str);
-            *index += str.chars().count();
-        }
-
-        let mut indices = Vec::new();
-        let mut index = 0;
-        let mut title = String::new();
-        let mut names_and_branches = names_and_branches.peekable();
-        while let Some((name, entry)) = names_and_branches.next() {
-            let pre_index = index;
-            push_str(&mut title, &mut index, name);
-            indices.extend((pre_index..index).into_iter());
-            if let Some(branch) = entry.and_then(|entry| entry.branch()) {
-                push_str(&mut title, &mut index, "/");
-                push_str(&mut title, &mut index, &branch);
-            }
-            if names_and_branches.peek().is_some() {
-                push_str(&mut title, &mut index, ", ");
-                if index >= MAX_TITLE_LENGTH {
-                    title.push_str(" …");
-                    break;
-                }
-            }
-        }
-
-        let text_style = theme.workspace.titlebar.title.clone();
-        let item_spacing = theme.workspace.titlebar.item_spacing;
+        let (name, entry) = names_and_branches.next().unwrap_or(("", None));
+        let branch_prepended = entry
+            .as_ref()
+            .and_then(RepositoryEntry::branch)
+            .map(|branch| format!("/{branch}"));
+        let text_style = theme.titlebar.title.clone();
+        let item_spacing = theme.titlebar.item_spacing;
 
         let mut highlight = text_style.clone();
-        highlight.color = theme.workspace.titlebar.highlight_color;
+        highlight.color = theme.titlebar.highlight_color;
 
         let style = LabelStyle {
             text: text_style,
             highlight_text: Some(highlight),
         };
-
-        Label::new(title, style)
-            .with_highlights(indices)
-            .contained()
-            .with_margin_right(item_spacing)
-            .aligned()
-            .left()
-            .into_any_named("title-with-git-information")
+        let mut ret = Flex::row().with_child(
+            Label::new(name.to_owned(), style.clone())
+                .with_highlights((0..name.len()).into_iter().collect())
+                .contained()
+                .aligned()
+                .left()
+                .into_any_named("title-project-name"),
+        );
+        if let Some(git_branch) = branch_prepended {
+            ret = ret.with_child(
+                Label::new(git_branch, style)
+                    .contained()
+                    .with_margin_right(item_spacing)
+                    .aligned()
+                    .left()
+                    .into_any_named("title-project-branch"),
+            )
+        }
+        ret.into_any()
     }
 
     fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
@@ -297,45 +294,29 @@ impl CollabTitlebarItem {
     }
 
     pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
-        let theme = theme::current(cx).clone();
-        let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
-        let item_style = theme.context_menu.item.disabled_style().clone();
         self.user_menu.update(cx, |user_menu, cx| {
-            let items = if let Some(user) = self.user_store.read(cx).current_user() {
+            let items = if let Some(_) = self.user_store.read(cx).current_user() {
                 vec![
-                    ContextMenuItem::Static(Box::new(move |_| {
-                        Flex::row()
-                            .with_children(user.avatar.clone().map(|avatar| {
-                                Self::render_face(
-                                    avatar,
-                                    avatar_style.clone(),
-                                    Color::transparent_black(),
-                                )
-                            }))
-                            .with_child(Label::new(
-                                user.github_login.clone(),
-                                item_style.label.clone(),
-                            ))
-                            .contained()
-                            .with_style(item_style.container)
-                            .into_any()
-                    })),
-                    ContextMenuItem::action("Sign out", SignOut),
+                    ContextMenuItem::action("Settings", zed_actions::OpenSettings),
+                    ContextMenuItem::action("Theme", theme_selector::Toggle),
+                    ContextMenuItem::separator(),
                     ContextMenuItem::action(
-                        "Send Feedback",
+                        "Share Feedback",
                         feedback::feedback_editor::GiveFeedback,
                     ),
+                    ContextMenuItem::action("Sign out", SignOut),
                 ]
             } else {
                 vec![
-                    ContextMenuItem::action("Sign in", SignIn),
+                    ContextMenuItem::action("Settings", zed_actions::OpenSettings),
+                    ContextMenuItem::action("Theme", theme_selector::Toggle),
+                    ContextMenuItem::separator(),
                     ContextMenuItem::action(
-                        "Send Feedback",
+                        "Share Feedback",
                         feedback::feedback_editor::GiveFeedback,
                     ),
                 ]
             };
-
             user_menu.show(Default::default(), AnchorCorner::TopRight, items, cx);
         });
     }
@@ -345,7 +326,7 @@ impl CollabTitlebarItem {
         theme: &Theme,
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
-        let titlebar = &theme.workspace.titlebar;
+        let titlebar = &theme.titlebar;
 
         let badge = if self
             .user_store
@@ -361,8 +342,20 @@ impl CollabTitlebarItem {
                     .contained()
                     .with_style(titlebar.toggle_contacts_badge)
                     .contained()
-                    .with_margin_left(titlebar.toggle_contacts_button.default.icon_width)
-                    .with_margin_top(titlebar.toggle_contacts_button.default.icon_width)
+                    .with_margin_left(
+                        titlebar
+                            .toggle_contacts_button
+                            .inactive_state()
+                            .default
+                            .icon_width,
+                    )
+                    .with_margin_top(
+                        titlebar
+                            .toggle_contacts_button
+                            .inactive_state()
+                            .default
+                            .icon_width,
+                    )
                     .aligned(),
             )
         };
@@ -372,8 +365,9 @@ impl CollabTitlebarItem {
                 MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
                     let style = titlebar
                         .toggle_contacts_button
-                        .style_for(state, self.contacts_popover.is_some());
-                    Svg::new("icons/user_plus_16.svg")
+                        .in_state(self.contacts_popover.is_some())
+                        .style_for(state);
+                    Svg::new("icons/radix/person.svg")
                         .with_color(style.color)
                         .constrained()
                         .with_width(style.icon_width)
@@ -400,7 +394,6 @@ impl CollabTitlebarItem {
             .with_children(self.render_contacts_popover_host(titlebar, cx))
             .into_any()
     }
-
     fn render_toggle_screen_sharing_button(
         &self,
         theme: &Theme,
@@ -410,16 +403,21 @@ impl CollabTitlebarItem {
         let icon;
         let tooltip;
         if room.read(cx).is_screen_sharing() {
-            icon = "icons/enable_screen_sharing_12.svg";
+            icon = "icons/radix/desktop.svg";
             tooltip = "Stop Sharing Screen"
         } else {
-            icon = "icons/disable_screen_sharing_12.svg";
+            icon = "icons/radix/desktop.svg";
             tooltip = "Share Screen";
         }
 
-        let titlebar = &theme.workspace.titlebar;
+        let active = room.read(cx).is_screen_sharing();
+        let titlebar = &theme.titlebar;
         MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
-            let style = titlebar.call_control.style_for(state, false);
+            let style = titlebar
+                .screen_share_button
+                .in_state(active)
+                .style_for(state);
+
             Svg::new(icon)
                 .with_color(style.color)
                 .constrained()
@@ -445,7 +443,141 @@ impl CollabTitlebarItem {
         .aligned()
         .into_any()
     }
+    fn render_toggle_mute(
+        &self,
+        theme: &Theme,
+        room: &ModelHandle<Room>,
+        cx: &mut ViewContext<Self>,
+    ) -> AnyElement<Self> {
+        let icon;
+        let tooltip;
+        let is_muted = room.read(cx).is_muted();
+        if is_muted {
+            icon = "icons/radix/mic-mute.svg";
+            tooltip = "Unmute microphone\nRight click for options";
+        } else {
+            icon = "icons/radix/mic.svg";
+            tooltip = "Mute microphone\nRight click for options";
+        }
 
+        let titlebar = &theme.titlebar;
+        MouseEventHandler::<ToggleMute, Self>::new(0, cx, |state, _| {
+            let style = titlebar
+                .toggle_microphone_button
+                .in_state(is_muted)
+                .style_for(state);
+            let image = Svg::new(icon)
+                .with_color(style.color)
+                .constrained()
+                .with_width(style.icon_width)
+                .aligned()
+                .constrained()
+                .with_width(style.button_width)
+                .with_height(style.button_width)
+                .contained()
+                .with_style(style.container);
+            if let Some(color) = style.container.background_color {
+                image.with_background_color(color)
+            } else {
+                image
+            }
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            toggle_mute(&Default::default(), cx)
+        })
+        .with_tooltip::<ToggleMute>(
+            0,
+            tooltip.into(),
+            Some(Box::new(ToggleMute)),
+            theme.tooltip.clone(),
+            cx,
+        )
+        .aligned()
+        .into_any()
+    }
+    fn render_toggle_deafen(
+        &self,
+        theme: &Theme,
+        room: &ModelHandle<Room>,
+        cx: &mut ViewContext<Self>,
+    ) -> AnyElement<Self> {
+        let icon;
+        let tooltip;
+        let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
+        if is_deafened {
+            icon = "icons/radix/speaker-off.svg";
+            tooltip = "Unmute speakers\nRight click for options";
+        } else {
+            icon = "icons/radix/speaker-loud.svg";
+            tooltip = "Mute speakers\nRight click for options";
+        }
+
+        let titlebar = &theme.titlebar;
+        MouseEventHandler::<ToggleDeafen, Self>::new(0, cx, |state, _| {
+            let style = titlebar
+                .toggle_speakers_button
+                .in_state(is_deafened)
+                .style_for(state);
+            Svg::new(icon)
+                .with_color(style.color)
+                .constrained()
+                .with_width(style.icon_width)
+                .aligned()
+                .constrained()
+                .with_width(style.button_width)
+                .with_height(style.button_width)
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            toggle_deafen(&Default::default(), cx)
+        })
+        .with_tooltip::<ToggleDeafen>(
+            0,
+            tooltip.into(),
+            Some(Box::new(ToggleDeafen)),
+            theme.tooltip.clone(),
+            cx,
+        )
+        .aligned()
+        .into_any()
+    }
+    fn render_leave_call(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        let icon = "icons/radix/exit.svg";
+        let tooltip = "Leave call";
+
+        let titlebar = &theme.titlebar;
+        MouseEventHandler::<LeaveCall, Self>::new(0, cx, |state, _| {
+            let style = titlebar.leave_call_button.style_for(state);
+            Svg::new(icon)
+                .with_color(style.color)
+                .constrained()
+                .with_width(style.icon_width)
+                .aligned()
+                .constrained()
+                .with_width(style.button_width)
+                .with_height(style.button_width)
+                .contained()
+                .with_style(style.container)
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            ActiveCall::global(cx)
+                .update(cx, |call, cx| call.hang_up(cx))
+                .detach_and_log_err(cx);
+        })
+        .with_tooltip::<LeaveCall>(
+            0,
+            tooltip.into(),
+            Some(Box::new(LeaveCall)),
+            theme.tooltip.clone(),
+            cx,
+        )
+        .aligned()
+        .into_any()
+    }
     fn render_in_call_share_unshare_button(
         &self,
         workspace: &ViewHandle<Workspace>,
@@ -458,14 +590,14 @@ impl CollabTitlebarItem {
         }
 
         let is_shared = project.read(cx).is_shared();
-        let label = if is_shared { "Unshare" } else { "Share" };
+        let label = if is_shared { "Stop Sharing" } else { "Share" };
         let tooltip = if is_shared {
-            "Unshare project from call participants"
+            "Stop sharing project with call participants"
         } else {
             "Share project with call participants"
         };
 
-        let titlebar = &theme.workspace.titlebar;
+        let titlebar = &theme.titlebar;
 
         enum ShareUnshare {}
         Some(
@@ -473,7 +605,7 @@ impl CollabTitlebarItem {
                 .with_child(
                     MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
                         //TODO: Ensure this button has consistent width for both text variations
-                        let style = titlebar.share_button.style_for(state, false);
+                        let style = titlebar.share_button.inactive_state().style_for(state);
                         Label::new(label, style.text.clone())
                             .contained()
                             .with_style(style.container)
@@ -496,7 +628,7 @@ impl CollabTitlebarItem {
                 )
                 .aligned()
                 .contained()
-                .with_margin_left(theme.workspace.titlebar.item_spacing)
+                .with_margin_left(theme.titlebar.item_spacing)
                 .into_any(),
         )
     }
@@ -504,24 +636,51 @@ impl CollabTitlebarItem {
     fn render_user_menu_button(
         &self,
         theme: &Theme,
+        avatar: Option<Arc<ImageData>>,
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
-        let titlebar = &theme.workspace.titlebar;
+        let tooltip = theme.tooltip.clone();
+        let user_menu_button_style = if avatar.is_some() {
+            &theme.titlebar.user_menu.user_menu_button_online
+        } else {
+            &theme.titlebar.user_menu.user_menu_button_offline
+        };
 
+        let avatar_style = &user_menu_button_style.avatar;
         Stack::new()
             .with_child(
                 MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
-                    let style = titlebar.call_control.style_for(state, false);
-                    Svg::new("icons/ellipsis_14.svg")
-                        .with_color(style.color)
-                        .constrained()
-                        .with_width(style.icon_width)
+                    let style = user_menu_button_style
+                        .user_menu
+                        .inactive_state()
+                        .style_for(state);
+
+                    let mut dropdown = Flex::row().align_children_center();
+
+                    if let Some(avatar_img) = avatar {
+                        dropdown = dropdown.with_child(Self::render_face(
+                            avatar_img,
+                            *avatar_style,
+                            Color::transparent_black(),
+                            None,
+                        ));
+                    };
+
+                    dropdown
+                        .with_child(
+                            Svg::new("icons/caret_down_8.svg")
+                                .with_color(user_menu_button_style.icon.color)
+                                .constrained()
+                                .with_width(user_menu_button_style.icon.width)
+                                .contained()
+                                .into_any(),
+                        )
                         .aligned()
                         .constrained()
-                        .with_width(style.button_width)
-                        .with_height(style.button_width)
+                        .with_height(style.width)
                         .contained()
                         .with_style(style.container)
+                        .into_any()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, move |_, this, cx| {
@@ -531,11 +690,10 @@ impl CollabTitlebarItem {
                     0,
                     "Toggle user menu".to_owned(),
                     Some(Box::new(ToggleUserMenu)),
-                    theme.tooltip.clone(),
+                    tooltip,
                     cx,
                 )
-                .contained()
-                .with_margin_left(theme.workspace.titlebar.item_spacing),
+                .contained(),
             )
             .with_child(
                 ChildView::new(&self.user_menu, cx)
@@ -547,9 +705,9 @@ impl CollabTitlebarItem {
     }
 
     fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let titlebar = &theme.workspace.titlebar;
+        let titlebar = &theme.titlebar;
         MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
-            let style = titlebar.sign_in_prompt.style_for(state, false);
+            let style = titlebar.sign_in_button.inactive_state().style_for(state);
             Label::new("Sign In", style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -611,11 +769,13 @@ impl CollabTitlebarItem {
                         replica_id,
                         participant.peer_id,
                         Some(participant.location),
+                        participant.muted,
+                        participant.speaking,
                         workspace,
                         theme,
                         cx,
                     ))
-                    .with_margin_right(theme.workspace.titlebar.face_pile_spacing),
+                    .with_margin_right(theme.titlebar.face_pile_spacing),
                 )
             })
             .collect()
@@ -627,19 +787,24 @@ impl CollabTitlebarItem {
         theme: &Theme,
         user: &Arc<User>,
         peer_id: PeerId,
+        muted: bool,
+        speaking: bool,
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
         let replica_id = workspace.read(cx).project().read(cx).replica_id();
+
         Container::new(self.render_face_pile(
             user,
             Some(replica_id),
             peer_id,
             None,
+            muted,
+            speaking,
             workspace,
             theme,
             cx,
         ))
-        .with_margin_right(theme.workspace.titlebar.item_spacing)
+        .with_margin_right(theme.titlebar.item_spacing)
         .into_any()
     }
 
@@ -649,6 +814,8 @@ impl CollabTitlebarItem {
         replica_id: Option<ReplicaId>,
         peer_id: PeerId,
         location: Option<ParticipantLocation>,
+        muted: bool,
+        speaking: bool,
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
         cx: &mut ViewContext<Self>,
@@ -671,15 +838,23 @@ impl CollabTitlebarItem {
             })
             .unwrap_or(false);
 
-        let leader_style = theme.workspace.titlebar.leader_avatar;
-        let follower_style = theme.workspace.titlebar.follower_avatar;
+        let leader_style = theme.titlebar.leader_avatar;
+        let follower_style = theme.titlebar.follower_avatar;
+
+        let microphone_state = if muted {
+            Some(theme.titlebar.muted)
+        } else if speaking {
+            Some(theme.titlebar.speaking)
+        } else {
+            None
+        };
 
         let mut background_color = theme
-            .workspace
             .titlebar
             .container
             .background_color
             .unwrap_or_default();
+
         if let Some(replica_id) = replica_id {
             if followed_by_self {
                 let selection = theme.editor.replica_selection_style(replica_id).selection;
@@ -690,11 +865,12 @@ impl CollabTitlebarItem {
 
         let mut content = Stack::new()
             .with_children(user.avatar.as_ref().map(|avatar| {
-                let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap)
+                let face_pile = FacePile::new(theme.titlebar.follower_avatar_overlap)
                     .with_child(Self::render_face(
                         avatar.clone(),
                         Self::location_style(workspace, location, leader_style, cx),
                         background_color,
+                        microphone_state,
                     ))
                     .with_children(
                         (|| {
@@ -726,6 +902,7 @@ impl CollabTitlebarItem {
                                     avatar.clone(),
                                     follower_style,
                                     background_color,
+                                    None,
                                 ))
                             }))
                         })()
@@ -735,7 +912,7 @@ impl CollabTitlebarItem {
 
                 let mut container = face_pile
                     .contained()
-                    .with_style(theme.workspace.titlebar.leader_selection);
+                    .with_style(theme.titlebar.leader_selection);
 
                 if let Some(replica_id) = replica_id {
                     if followed_by_self {
@@ -752,8 +929,8 @@ impl CollabTitlebarItem {
                 Some(
                     AvatarRibbon::new(color)
                         .constrained()
-                        .with_width(theme.workspace.titlebar.avatar_ribbon.width)
-                        .with_height(theme.workspace.titlebar.avatar_ribbon.height)
+                        .with_width(theme.titlebar.avatar_ribbon.width)
+                        .with_height(theme.titlebar.avatar_ribbon.height)
                         .aligned()
                         .bottom(),
                 )
@@ -844,12 +1021,13 @@ impl CollabTitlebarItem {
         avatar: Arc<ImageData>,
         avatar_style: AvatarStyle,
         background_color: Color,
+        microphone_state: Option<Color>,
     ) -> AnyElement<V> {
         Image::from_data(avatar)
             .with_style(avatar_style.image)
             .aligned()
             .contained()
-            .with_background_color(background_color)
+            .with_background_color(microphone_state.unwrap_or(background_color))
             .with_corner_radius(avatar_style.outer_corner_radius)
             .constrained()
             .with_width(avatar_style.outer_width)
@@ -873,22 +1051,22 @@ impl CollabTitlebarItem {
             | client::Status::Reconnecting { .. }
             | client::Status::ReconnectionError { .. } => Some(
                 Svg::new("icons/cloud_slash_12.svg")
-                    .with_color(theme.workspace.titlebar.offline_icon.color)
+                    .with_color(theme.titlebar.offline_icon.color)
                     .constrained()
-                    .with_width(theme.workspace.titlebar.offline_icon.width)
+                    .with_width(theme.titlebar.offline_icon.width)
                     .aligned()
                     .contained()
-                    .with_style(theme.workspace.titlebar.offline_icon.container)
+                    .with_style(theme.titlebar.offline_icon.container)
                     .into_any(),
             ),
             client::Status::UpgradeRequired => Some(
                 MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
                     Label::new(
                         "Please update Zed to collaborate",
-                        theme.workspace.titlebar.outdated_warning.text.clone(),
+                        theme.titlebar.outdated_warning.text.clone(),
                     )
                     .contained()
-                    .with_style(theme.workspace.titlebar.outdated_warning.container)
+                    .with_style(theme.titlebar.outdated_warning.container)
                     .aligned()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)

crates/collab_ui/src/collab_ui.rs πŸ”—

@@ -9,13 +9,23 @@ mod notifications;
 mod project_shared_notification;
 mod sharing_status_indicator;
 
-use call::ActiveCall;
+use call::{ActiveCall, Room};
 pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
 use gpui::{actions, AppContext, Task};
 use std::sync::Arc;
+use util::ResultExt;
 use workspace::AppState;
 
-actions!(collab, [ToggleScreenSharing]);
+actions!(
+    collab,
+    [
+        ToggleScreenSharing,
+        ToggleMute,
+        ToggleDeafen,
+        LeaveCall,
+        ShareMicrophone
+    ]
+);
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     collab_titlebar_item::init(cx);
@@ -27,6 +37,9 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     sharing_status_indicator::init(cx);
 
     cx.add_global_action(toggle_screen_sharing);
+    cx.add_global_action(toggle_mute);
+    cx.add_global_action(toggle_deafen);
+    cx.add_global_action(share_microphone);
 }
 
 pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -41,3 +54,26 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
         toggle_screen_sharing.detach_and_log_err(cx);
     }
 }
+
+pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
+    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+        room.update(cx, Room::toggle_mute)
+            .map(|task| task.detach_and_log_err(cx))
+            .log_err();
+    }
+}
+
+pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
+    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+        room.update(cx, Room::toggle_deafen)
+            .map(|task| task.detach_and_log_err(cx))
+            .log_err();
+    }
+}
+
+pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) {
+    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+        room.update(cx, Room::share_microphone)
+            .detach_and_log_err(cx)
+    }
+}

crates/collab_ui/src/contact_finder.rs πŸ”—

@@ -117,7 +117,8 @@ impl PickerDelegate for ContactFinderDelegate {
             .contact_finder
             .picker
             .item
-            .style_for(mouse_state, selected);
+            .in_state(selected)
+            .style_for(mouse_state);
         Flex::row()
             .with_children(user.avatar.clone().map(|avatar| {
                 Image::from_data(avatar)

crates/collab_ui/src/contact_list.rs πŸ”—

@@ -514,10 +514,10 @@ impl ContactList {
                         project_id: project.id,
                         worktree_root_names: project.worktree_root_names.clone(),
                         host_user_id: participant.user.id,
-                        is_last: projects.peek().is_none() && participant.tracks.is_empty(),
+                        is_last: projects.peek().is_none() && participant.video_tracks.is_empty(),
                     });
                 }
-                if !participant.tracks.is_empty() {
+                if !participant.video_tracks.is_empty() {
                     participant_entries.push(ContactEntry::ParticipantScreen {
                         peer_id: participant.peer_id,
                         is_last: true,
@@ -774,7 +774,8 @@ impl ContactList {
             .with_style(
                 *theme
                     .contact_row
-                    .style_for(&mut Default::default(), is_selected),
+                    .in_state(is_selected)
+                    .style_for(&mut Default::default()),
             )
             .into_any()
     }
@@ -797,7 +798,7 @@ impl ContactList {
             .width
             .or(theme.contact_avatar.height)
             .unwrap_or(0.);
-        let row = &theme.project_row.default;
+        let row = &theme.project_row.inactive_state().default;
         let tree_branch = theme.tree_branch;
         let line_height = row.name.text.line_height(font_cache);
         let cap_height = row.name.text.cap_height(font_cache);
@@ -810,8 +811,11 @@ impl ContactList {
         };
 
         MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
-            let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
-            let row = theme.project_row.style_for(mouse_state, is_selected);
+            let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+            let row = theme
+                .project_row
+                .in_state(is_selected)
+                .style_for(mouse_state);
 
             Flex::row()
                 .with_child(
@@ -893,7 +897,7 @@ impl ContactList {
             .width
             .or(theme.contact_avatar.height)
             .unwrap_or(0.);
-        let row = &theme.project_row.default;
+        let row = &theme.project_row.inactive_state().default;
         let tree_branch = theme.tree_branch;
         let line_height = row.name.text.line_height(font_cache);
         let cap_height = row.name.text.cap_height(font_cache);
@@ -904,8 +908,11 @@ impl ContactList {
             peer_id.as_u64() as usize,
             cx,
             |mouse_state, _| {
-                let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
-                let row = theme.project_row.style_for(mouse_state, is_selected);
+                let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+                let row = theme
+                    .project_row
+                    .in_state(is_selected)
+                    .style_for(mouse_state);
 
                 Flex::row()
                     .with_child(
@@ -989,7 +996,8 @@ impl ContactList {
 
         let header_style = theme
             .header_row
-            .style_for(&mut Default::default(), is_selected);
+            .in_state(is_selected)
+            .style_for(&mut Default::default());
         let text = match section {
             Section::ActiveCall => "Collaborators",
             Section::Requests => "Contact Requests",
@@ -999,7 +1007,7 @@ impl ContactList {
         let leave_call = if section == Section::ActiveCall {
             Some(
                 MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
-                    let style = theme.leave_call.style_for(state, false);
+                    let style = theme.leave_call.style_for(state);
                     Label::new("Leave Call", style.text.clone())
                         .contained()
                         .with_style(style.container)
@@ -1110,8 +1118,7 @@ impl ContactList {
                             contact.user.id as usize,
                             cx,
                             |mouse_state, _| {
-                                let button_style =
-                                    theme.contact_button.style_for(mouse_state, false);
+                                let button_style = theme.contact_button.style_for(mouse_state);
                                 render_icon_button(button_style, "icons/x_mark_8.svg")
                                     .aligned()
                                     .flex_float()
@@ -1146,7 +1153,8 @@ impl ContactList {
                     .with_style(
                         *theme
                             .contact_row
-                            .style_for(&mut Default::default(), is_selected),
+                            .in_state(is_selected)
+                            .style_for(&mut Default::default()),
                     )
             })
             .on_click(MouseButton::Left, move |_, this, cx| {
@@ -1204,7 +1212,7 @@ impl ContactList {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
-                        theme.contact_button.style_for(mouse_state, false)
+                        theme.contact_button.style_for(mouse_state)
                     };
                     render_icon_button(button_style, "icons/x_mark_8.svg").aligned()
                 })
@@ -1227,7 +1235,7 @@ impl ContactList {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
-                        theme.contact_button.style_for(mouse_state, false)
+                        theme.contact_button.style_for(mouse_state)
                     };
                     render_icon_button(button_style, "icons/check_8.svg")
                         .aligned()
@@ -1250,7 +1258,7 @@ impl ContactList {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
-                        theme.contact_button.style_for(mouse_state, false)
+                        theme.contact_button.style_for(mouse_state)
                     };
                     render_icon_button(button_style, "icons/x_mark_8.svg")
                         .aligned()
@@ -1277,7 +1285,8 @@ impl ContactList {
             .with_style(
                 *theme
                     .contact_row
-                    .style_for(&mut Default::default(), is_selected),
+                    .in_state(is_selected)
+                    .style_for(&mut Default::default()),
             )
             .into_any()
     }

crates/collab_ui/src/notifications.rs πŸ”—

@@ -53,7 +53,7 @@ where
                 )
                 .with_child(
                     MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
-                        let style = theme.dismiss_button.style_for(state, false);
+                        let style = theme.dismiss_button.style_for(state);
                         Svg::new("icons/x_mark_8.svg")
                             .with_color(style.color)
                             .constrained()
@@ -93,7 +93,7 @@ where
                     .with_children(buttons.into_iter().enumerate().map(
                         |(ix, (message, handler))| {
                             MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
-                                let button = theme.button.style_for(state, false);
+                                let button = theme.button.style_for(state);
                                 Label::new(message, button.text.clone())
                                     .contained()
                                     .with_style(button.container)

crates/command_palette/src/command_palette.rs πŸ”—

@@ -185,8 +185,8 @@ impl PickerDelegate for CommandPaletteDelegate {
         let mat = &self.matches[ix];
         let command = &self.actions[mat.candidate_id];
         let theme = theme::current(cx);
-        let style = theme.picker.item.style_for(mouse_state, selected);
-        let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
+        let key_style = &theme.command_palette.key.in_state(selected);
         let keystroke_spacing = theme.command_palette.keystroke_spacing;
 
         Flex::row()

crates/context_menu/src/context_menu.rs πŸ”—

@@ -328,10 +328,8 @@ impl ContextMenu {
                 Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
                     match item {
                         ContextMenuItem::Item { label, .. } => {
-                            let style = style.item.style_for(
-                                &mut Default::default(),
-                                Some(ix) == self.selected_index,
-                            );
+                            let style = style.item.in_state(self.selected_index == Some(ix));
+                            let style = style.style_for(&mut Default::default());
 
                             match label {
                                 ContextMenuItemLabel::String(label) => {
@@ -363,10 +361,8 @@ impl ContextMenu {
                     .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                         match item {
                             ContextMenuItem::Item { action, .. } => {
-                                let style = style.item.style_for(
-                                    &mut Default::default(),
-                                    Some(ix) == self.selected_index,
-                                );
+                                let style = style.item.in_state(self.selected_index == Some(ix));
+                                let style = style.style_for(&mut Default::default());
 
                                 match action {
                                     ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
@@ -412,8 +408,8 @@ impl ContextMenu {
                             let action = action.clone();
                             let view_id = self.parent_view_id;
                             MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
-                                let style =
-                                    style.item.style_for(state, Some(ix) == self.selected_index);
+                                let style = style.item.in_state(self.selected_index == Some(ix));
+                                let style = style.style_for(state);
                                 let keystroke = match &action {
                                     ContextMenuItemAction::Action(action) => Some(
                                         KeystrokeLabel::new(

crates/copilot/src/sign_in.rs πŸ”—

@@ -127,16 +127,16 @@ impl CopilotCodeVerification {
                 .with_child(
                     Label::new(
                         if copied { "Copied!" } else { "Copy" },
-                        device_code_style.cta.style_for(state, false).text.clone(),
+                        device_code_style.cta.style_for(state).text.clone(),
                     )
                     .aligned()
                     .contained()
-                    .with_style(*device_code_style.right_container.style_for(state, false))
+                    .with_style(*device_code_style.right_container.style_for(state))
                     .constrained()
                     .with_width(device_code_style.right),
                 )
                 .contained()
-                .with_style(device_code_style.cta.style_for(state, false).container)
+                .with_style(device_code_style.cta.style_for(state).container)
         })
         .on_click(gpui::platform::MouseButton::Left, {
             let user_code = data.user_code.clone();

crates/copilot_button/src/copilot_button.rs πŸ”—

@@ -71,7 +71,8 @@ impl View for CopilotButton {
                             .status_bar
                             .panel_buttons
                             .button
-                            .style_for(state, active);
+                            .in_state(active)
+                            .style_for(state);
 
                         Flex::row()
                             .with_child(
@@ -255,7 +256,7 @@ impl CopilotButton {
             move |state: &mut MouseState, style: &theme::ContextMenuItem| {
                 Flex::row()
                     .with_child(Label::new("Copilot Settings", style.label.clone()))
-                    .with_child(theme::ui::icon(icon_style.style_for(state, false)))
+                    .with_child(theme::ui::icon(icon_style.style_for(state)))
                     .align_children_center()
                     .into_any()
             },

crates/diagnostics/src/diagnostics.rs πŸ”—

@@ -1509,7 +1509,8 @@ mod tests {
             let snapshot = editor.snapshot(cx);
             snapshot
                 .blocks_in_range(0..snapshot.max_point().row())
-                .filter_map(|(row, block)| {
+                .enumerate()
+                .filter_map(|(ix, (row, block))| {
                     let name = match block {
                         TransformBlock::Custom(block) => block
                             .render(&mut BlockContext {
@@ -1520,6 +1521,7 @@ mod tests {
                                 gutter_width: 0.,
                                 line_height: 0.,
                                 em_width: 0.,
+                                block_id: ix,
                             })
                             .name()?
                             .to_string(),

crates/diagnostics/src/items.rs πŸ”—

@@ -100,7 +100,7 @@ impl View for DiagnosticIndicator {
                     .workspace
                     .status_bar
                     .diagnostic_summary
-                    .style_for(state, false);
+                    .style_for(state);
 
                 let mut summary_row = Flex::row();
                 if self.summary.error_count > 0 {
@@ -198,7 +198,7 @@ impl View for DiagnosticIndicator {
                 MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
                     Label::new(
                         diagnostic.message.split('\n').next().unwrap().to_string(),
-                        message_style.style_for(state, false).text.clone(),
+                        message_style.style_for(state).text.clone(),
                     )
                     .aligned()
                     .contained()

crates/editor/src/display_map/block_map.rs πŸ”—

@@ -88,6 +88,7 @@ pub struct BlockContext<'a, 'b, 'c> {
     pub gutter_padding: f32,
     pub em_width: f32,
     pub line_height: f32,
+    pub block_id: usize,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]

crates/editor/src/editor.rs πŸ”—

@@ -206,6 +206,7 @@ actions!(
         DuplicateLine,
         MoveLineUp,
         MoveLineDown,
+        JoinLines,
         Transpose,
         Cut,
         Copy,
@@ -321,6 +322,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(Editor::indent);
     cx.add_action(Editor::outdent);
     cx.add_action(Editor::delete_line);
+    cx.add_action(Editor::join_lines);
     cx.add_action(Editor::delete_to_previous_word_start);
     cx.add_action(Editor::delete_to_previous_subword_start);
     cx.add_action(Editor::delete_to_next_word_end);
@@ -3320,15 +3322,21 @@ impl Editor {
     pub fn render_code_actions_indicator(
         &self,
         style: &EditorStyle,
-        active: bool,
+        is_active: bool,
         cx: &mut ViewContext<Self>,
     ) -> Option<AnyElement<Self>> {
         if self.available_code_actions.is_some() {
             enum CodeActions {}
             Some(
                 MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
-                    Svg::new("icons/bolt_8.svg")
-                        .with_color(style.code_actions.indicator.style_for(state, active).color)
+                    Svg::new("icons/bolt_8.svg").with_color(
+                        style
+                            .code_actions
+                            .indicator
+                            .in_state(is_active)
+                            .style_for(state)
+                            .color,
+                    )
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .with_padding(Padding::uniform(3.))
@@ -3378,10 +3386,8 @@ impl Editor {
                                     .with_color(
                                         style
                                             .indicator
-                                            .style_for(
-                                                mouse_state,
-                                                fold_status == FoldStatus::Folded,
-                                            )
+                                            .in_state(fold_status == FoldStatus::Folded)
+                                            .style_for(mouse_state)
                                             .color,
                                     )
                                     .constrained()
@@ -3952,6 +3958,60 @@ impl Editor {
         });
     }
 
+    pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
+        let mut row_ranges = Vec::<Range<u32>>::new();
+        for selection in self.selections.all::<Point>(cx) {
+            let start = selection.start.row;
+            let end = if selection.start.row == selection.end.row {
+                selection.start.row + 1
+            } else {
+                selection.end.row
+            };
+
+            if let Some(last_row_range) = row_ranges.last_mut() {
+                if start <= last_row_range.end {
+                    last_row_range.end = end;
+                    continue;
+                }
+            }
+            row_ranges.push(start..end);
+        }
+
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let mut cursor_positions = Vec::new();
+        for row_range in &row_ranges {
+            let anchor = snapshot.anchor_before(Point::new(
+                row_range.end - 1,
+                snapshot.line_len(row_range.end - 1),
+            ));
+            cursor_positions.push(anchor.clone()..anchor);
+        }
+
+        self.transact(cx, |this, cx| {
+            for row_range in row_ranges.into_iter().rev() {
+                for row in row_range.rev() {
+                    let end_of_line = Point::new(row, snapshot.line_len(row));
+                    let indent = snapshot.indent_size_for_line(row + 1);
+                    let start_of_next_line = Point::new(row + 1, indent.len);
+
+                    let replace = if snapshot.line_len(row + 1) > indent.len {
+                        " "
+                    } else {
+                        ""
+                    };
+
+                    this.buffer.update(cx, |buffer, cx| {
+                        buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
+                    });
+                }
+            }
+
+            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.select_anchor_ranges(cursor_positions)
+            });
+        });
+    }
+
     pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = &display_map.buffer_snapshot;
@@ -7581,8 +7641,14 @@ impl View for Editor {
             keymap.add_identifier("renaming");
         }
         match self.context_menu.as_ref() {
-            Some(ContextMenu::Completions(_)) => keymap.add_identifier("showing_completions"),
-            Some(ContextMenu::CodeActions(_)) => keymap.add_identifier("showing_code_actions"),
+            Some(ContextMenu::Completions(_)) => {
+                keymap.add_identifier("menu");
+                keymap.add_identifier("showing_completions")
+            }
+            Some(ContextMenu::CodeActions(_)) => {
+                keymap.add_identifier("menu");
+                keymap.add_identifier("showing_code_actions")
+            }
             None => {}
         }
         for layer in self.keymap_context_layers.values() {
@@ -7949,6 +8015,7 @@ impl Deref for EditorStyle {
 
 pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> RenderBlock {
     let mut highlighted_lines = Vec::new();
+
     for (index, line) in diagnostic.message.lines().enumerate() {
         let line = match &diagnostic.source {
             Some(source) if index == 0 => {
@@ -7960,25 +8027,44 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
         };
         highlighted_lines.push(line);
     }
-
+    let message = diagnostic.message;
     Arc::new(move |cx: &mut BlockContext| {
+        let message = message.clone();
         let settings = settings::get::<ThemeSettings>(cx);
+        let tooltip_style = settings.theme.tooltip.clone();
         let theme = &settings.theme.editor;
         let style = diagnostic_style(diagnostic.severity, is_valid, theme);
         let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
-        Flex::column()
-            .with_children(highlighted_lines.iter().map(|(line, highlights)| {
-                Label::new(
-                    line.clone(),
-                    style.message.clone().with_font_size(font_size),
-                )
-                .with_highlights(highlights.clone())
-                .contained()
-                .with_margin_left(cx.anchor_x)
-            }))
-            .aligned()
-            .left()
-            .into_any()
+        let anchor_x = cx.anchor_x;
+        enum BlockContextToolip {}
+        MouseEventHandler::<BlockContext, _>::new(cx.block_id, cx, |_, _| {
+            Flex::column()
+                .with_children(highlighted_lines.iter().map(|(line, highlights)| {
+                    Label::new(
+                        line.clone(),
+                        style.message.clone().with_font_size(font_size),
+                    )
+                    .with_highlights(highlights.clone())
+                    .contained()
+                    .with_margin_left(anchor_x)
+                }))
+                .aligned()
+                .left()
+                .into_any()
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            cx.write_to_clipboard(ClipboardItem::new(message.clone()));
+        })
+        // We really need to rethink this ID system...
+        .with_tooltip::<BlockContextToolip>(
+            cx.block_id,
+            "Copy diagnostic message".to_string(),
+            None,
+            tooltip_style,
+            cx,
+        )
+        .into_any()
     })
 }
 

crates/editor/src/editor_tests.rs πŸ”—

@@ -1,7 +1,11 @@
 use super::*;
-use crate::test::{
-    assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
-    editor_test_context::EditorTestContext, select_ranges,
+use crate::{
+    scroll::scroll_amount::ScrollAmount,
+    test::{
+        assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
+        editor_test_context::EditorTestContext, select_ranges,
+    },
+    JoinLines,
 };
 use drag_and_drop::DragAndDrop;
 use futures::StreamExt;
@@ -1356,6 +1360,43 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
     );
 }
 
+#[gpui::test]
+async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    let mut cx = EditorTestContext::new(cx).await;
+    let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+    cx.simulate_window_resize(cx.window_id, vec2f(1000., 4. * line_height + 0.5));
+
+    cx.set_state(
+        &r#"Λ‡one
+        two
+        three
+        four
+        five
+        six
+        seven
+        eight
+        nine
+        ten
+        "#,
+    );
+
+    cx.update_editor(|editor, cx| {
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.));
+        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
+        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 6.));
+        editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
+
+        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.));
+        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
+        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
+    });
+}
+
 #[gpui::test]
 async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
     init_test(cx, |_| {});
@@ -2325,6 +2366,137 @@ fn test_delete_line(cx: &mut TestAppContext) {
     });
 }
 
+#[gpui::test]
+fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+        let mut editor = build_editor(buffer.clone(), cx);
+        let buffer = buffer.read(cx).as_singleton().unwrap();
+
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            &[Point::new(0, 0)..Point::new(0, 0)]
+        );
+
+        // When on single line, replace newline at end by space
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            &[Point::new(0, 3)..Point::new(0, 3)]
+        );
+
+        // When multiple lines are selected, remove newlines that are spanned by the selection
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
+        });
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            &[Point::new(0, 11)..Point::new(0, 11)]
+        );
+
+        // Undo should be transactional
+        editor.undo(&Undo, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            &[Point::new(0, 5)..Point::new(2, 2)]
+        );
+
+        // When joining an empty line don't insert a space
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
+        });
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [Point::new(2, 3)..Point::new(2, 3)]
+        );
+
+        // We can remove trailing newlines
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [Point::new(2, 3)..Point::new(2, 3)]
+        );
+
+        // We don't blow up on the last line
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [Point::new(2, 3)..Point::new(2, 3)]
+        );
+
+        // reset to test indentation
+        editor.buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                [
+                    (Point::new(1, 0)..Point::new(1, 2), "  "),
+                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
+                ],
+                None,
+                cx,
+            )
+        });
+
+        // We remove any leading spaces
+        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
+        });
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
+
+        // We don't insert a space for a line containing only spaces
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
+
+        // We ignore any leading tabs
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
+
+        editor
+    });
+}
+
+#[gpui::test]
+fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+        let mut editor = build_editor(buffer.clone(), cx);
+        let buffer = buffer.read(cx).as_singleton().unwrap();
+
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([
+                Point::new(0, 2)..Point::new(1, 1),
+                Point::new(1, 2)..Point::new(1, 2),
+                Point::new(3, 1)..Point::new(3, 2),
+            ])
+        });
+
+        editor.join_lines(&JoinLines, cx);
+        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
+
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 7)..Point::new(0, 7),
+                Point::new(1, 3)..Point::new(1, 3)
+            ]
+        );
+        editor
+    });
+}
+
 #[gpui::test]
 fn test_duplicate_line(cx: &mut TestAppContext) {
     init_test(cx, |_| {});

crates/editor/src/element.rs πŸ”—

@@ -1467,6 +1467,7 @@ impl EditorElement {
         editor: &mut Editor,
         cx: &mut LayoutContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
+        let mut block_id = 0;
         let scroll_x = snapshot.scroll_anchor.offset.x();
         let (fixed_blocks, non_fixed_blocks) = snapshot
             .blocks_in_range(rows.clone())
@@ -1474,7 +1475,7 @@ impl EditorElement {
                 TransformBlock::ExcerptHeader { .. } => false,
                 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
             });
-        let mut render_block = |block: &TransformBlock, width: f32| {
+        let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
             let mut element = match block {
                 TransformBlock::Custom(block) => {
                     let align_to = block
@@ -1499,6 +1500,7 @@ impl EditorElement {
                         scroll_x,
                         gutter_width,
                         em_width,
+                        block_id,
                     })
                 }
                 TransformBlock::ExcerptHeader {
@@ -1527,7 +1529,7 @@ impl EditorElement {
 
                         enum JumpIcon {}
                         MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
-                            let style = style.jump_icon.style_for(state, false);
+                            let style = style.jump_icon.style_for(state);
                             Svg::new("icons/arrow_up_right_8.svg")
                                 .with_color(style.color)
                                 .constrained()
@@ -1634,7 +1636,8 @@ impl EditorElement {
         let mut fixed_block_max_width = 0f32;
         let mut blocks = Vec::new();
         for (row, block) in fixed_blocks {
-            let element = render_block(block, f32::INFINITY);
+            let element = render_block(block, f32::INFINITY, block_id);
+            block_id += 1;
             fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
             blocks.push(BlockLayout {
                 row,
@@ -1654,7 +1657,8 @@ impl EditorElement {
                     .max(gutter_width + scroll_width),
                 BlockStyle::Fixed => unreachable!(),
             };
-            let element = render_block(block, width);
+            let element = render_block(block, width, block_id);
+            block_id += 1;
             blocks.push(BlockLayout {
                 row,
                 element,
@@ -2090,7 +2094,7 @@ impl Element<Editor> for EditorElement {
                     .folds
                     .ellipses
                     .background
-                    .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize), false)
+                    .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
                     .color;
 
                 (id, fold, color)

crates/editor/src/scroll.rs πŸ”—

@@ -368,7 +368,7 @@ impl Editor {
         }
 
         let cur_position = self.scroll_position(cx);
-        let new_pos = cur_position + vec2f(0., amount.lines(self) - 1.);
+        let new_pos = cur_position + vec2f(0., amount.lines(self));
         self.set_scroll_position(new_pos, cx);
     }
 

crates/editor/src/scroll/actions.rs πŸ”—

@@ -27,22 +27,22 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(Editor::scroll_cursor_center);
     cx.add_action(Editor::scroll_cursor_bottom);
     cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
-        this.scroll_screen(&ScrollAmount::LineDown, cx)
+        this.scroll_screen(&ScrollAmount::Line(1.), cx)
     });
     cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
-        this.scroll_screen(&ScrollAmount::LineUp, cx)
+        this.scroll_screen(&ScrollAmount::Line(-1.), cx)
     });
     cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
-        this.scroll_screen(&ScrollAmount::HalfPageDown, cx)
+        this.scroll_screen(&ScrollAmount::Page(0.5), cx)
     });
     cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
-        this.scroll_screen(&ScrollAmount::HalfPageUp, cx)
+        this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
     });
     cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
-        this.scroll_screen(&ScrollAmount::PageDown, cx)
+        this.scroll_screen(&ScrollAmount::Page(1.), cx)
     });
     cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
-        this.scroll_screen(&ScrollAmount::PageUp, cx)
+        this.scroll_screen(&ScrollAmount::Page(-1.), cx)
     });
 }
 

crates/editor/src/scroll/scroll_amount.rs πŸ”—

@@ -6,12 +6,10 @@ use crate::Editor;
 
 #[derive(Clone, PartialEq, Deserialize)]
 pub enum ScrollAmount {
-    LineUp,
-    LineDown,
-    HalfPageUp,
-    HalfPageDown,
-    PageUp,
-    PageDown,
+    // Scroll N lines (positive is towards the end of the document)
+    Line(f32),
+    // Scroll N pages (positive is towards the end of the document)
+    Page(f32),
 }
 
 impl ScrollAmount {
@@ -24,10 +22,10 @@ impl ScrollAmount {
             let context_menu = editor.context_menu.as_mut()?;
 
             match self {
-                Self::LineDown | Self::HalfPageDown => context_menu.select_next(cx),
-                Self::LineUp | Self::HalfPageUp => context_menu.select_prev(cx),
-                Self::PageDown => context_menu.select_last(cx),
-                Self::PageUp => context_menu.select_first(cx),
+                Self::Line(c) if *c > 0. => context_menu.select_next(cx),
+                Self::Line(_) => context_menu.select_prev(cx),
+                Self::Page(c) if *c > 0. => context_menu.select_last(cx),
+                Self::Page(_) => context_menu.select_first(cx),
             }
             .then_some(())
         })
@@ -36,13 +34,13 @@ impl ScrollAmount {
 
     pub fn lines(&self, editor: &mut Editor) -> f32 {
         match self {
-            Self::LineDown => 1.,
-            Self::LineUp => -1.,
-            Self::HalfPageDown => editor.visible_line_count().map(|l| l / 2.).unwrap_or(1.),
-            Self::HalfPageUp => -editor.visible_line_count().map(|l| l / 2.).unwrap_or(1.),
-            // Minus 1. here so that there is a pivot line that stays on the screen
-            Self::PageDown => editor.visible_line_count().unwrap_or(1.) - 1.,
-            Self::PageUp => -editor.visible_line_count().unwrap_or(1.) - 1.,
+            Self::Line(count) => *count,
+            Self::Page(count) => editor
+                .visible_line_count()
+                // subtract one to leave an anchor line
+                // round towards zero (so page-up and page-down are symmetric)
+                .map(|l| ((l - 1.) * count).trunc())
+                .unwrap_or(0.),
         }
     }
 }

crates/feedback/src/deploy_feedback_button.rs πŸ”—

@@ -41,7 +41,8 @@ impl View for DeployFeedbackButton {
                         .status_bar
                         .panel_buttons
                         .button
-                        .style_for(state, active);
+                        .in_state(active)
+                        .style_for(state);
 
                     Svg::new("icons/feedback_16.svg")
                         .with_color(style.icon_color)

crates/feedback/src/submit_feedback_button.rs πŸ”—

@@ -48,7 +48,7 @@ impl View for SubmitFeedbackButton {
         let theme = theme::current(cx).clone();
         enum SubmitFeedbackButton {}
         MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
-            let style = theme.feedback.submit_button.style_for(state, false);
+            let style = theme.feedback.submit_button.style_for(state);
             Label::new("Submit as Markdown", style.text.clone())
                 .contained()
                 .with_style(style.container)

crates/file_finder/src/file_finder.rs πŸ”—

@@ -546,7 +546,7 @@ impl PickerDelegate for FileFinderDelegate {
             .get(ix)
             .expect("Invalid matches state: no element for index {ix}");
         let theme = theme::current(cx);
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         let (file_name, file_name_positions, full_path, full_path_positions) =
             self.labels_for_match(path_match, cx, ix);
         Flex::column()

crates/fs/Cargo.toml πŸ”—

@@ -32,5 +32,8 @@ serde_json.workspace = true
 log.workspace = true
 libc = "0.2"
 
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }
+
 [features]
 test-support = []

crates/fs/src/fs.rs πŸ”—

@@ -108,6 +108,7 @@ pub trait Fs: Send + Sync {
     async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
     async fn is_file(&self, path: &Path) -> bool;
     async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
+    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
     async fn read_dir(
         &self,
         path: &Path,
@@ -323,6 +324,11 @@ impl Fs for RealFs {
         }))
     }
 
+    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
+        let path = smol::fs::read_link(path).await?;
+        Ok(path)
+    }
+
     async fn read_dir(
         &self,
         path: &Path,
@@ -382,6 +388,7 @@ struct FakeFsState {
     event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
     events_paused: bool,
     buffered_events: Vec<fsevent::Event>,
+    read_dir_call_count: usize,
 }
 
 #[cfg(any(test, feature = "test-support"))]
@@ -407,46 +414,51 @@ enum FakeFsEntry {
 impl FakeFsState {
     fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
         Ok(self
-            .try_read_path(target)
+            .try_read_path(target, true)
             .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))?
             .0)
     }
 
-    fn try_read_path<'a>(&'a self, target: &Path) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
+    fn try_read_path<'a>(
+        &'a self,
+        target: &Path,
+        follow_symlink: bool,
+    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
         let mut path = target.to_path_buf();
-        let mut real_path = PathBuf::new();
+        let mut canonical_path = PathBuf::new();
         let mut entry_stack = Vec::new();
         'outer: loop {
-            let mut path_components = path.components().collect::<collections::VecDeque<_>>();
-            while let Some(component) = path_components.pop_front() {
+            let mut path_components = path.components().peekable();
+            while let Some(component) = path_components.next() {
                 match component {
                     Component::Prefix(_) => panic!("prefix paths aren't supported"),
                     Component::RootDir => {
                         entry_stack.clear();
                         entry_stack.push(self.root.clone());
-                        real_path.clear();
-                        real_path.push("/");
+                        canonical_path.clear();
+                        canonical_path.push("/");
                     }
                     Component::CurDir => {}
                     Component::ParentDir => {
                         entry_stack.pop()?;
-                        real_path.pop();
+                        canonical_path.pop();
                     }
                     Component::Normal(name) => {
                         let current_entry = entry_stack.last().cloned()?;
                         let current_entry = current_entry.lock();
                         if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
                             let entry = entries.get(name.to_str().unwrap()).cloned()?;
-                            let _entry = entry.lock();
-                            if let FakeFsEntry::Symlink { target, .. } = &*_entry {
-                                let mut target = target.clone();
-                                target.extend(path_components);
-                                path = target;
-                                continue 'outer;
-                            } else {
-                                entry_stack.push(entry.clone());
-                                real_path.push(name);
+                            if path_components.peek().is_some() || follow_symlink {
+                                let entry = entry.lock();
+                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
+                                    let mut target = target.clone();
+                                    target.extend(path_components);
+                                    path = target;
+                                    continue 'outer;
+                                }
                             }
+                            entry_stack.push(entry.clone());
+                            canonical_path.push(name);
                         } else {
                             return None;
                         }
@@ -455,7 +467,7 @@ impl FakeFsState {
             }
             break;
         }
-        entry_stack.pop().map(|entry| (entry, real_path))
+        Some((entry_stack.pop()?, canonical_path))
     }
 
     fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
@@ -525,6 +537,7 @@ impl FakeFs {
                 event_txs: Default::default(),
                 buffered_events: Vec::new(),
                 events_paused: false,
+                read_dir_call_count: 0,
             }),
         })
     }
@@ -761,6 +774,10 @@ impl FakeFs {
         result
     }
 
+    pub fn read_dir_call_count(&self) -> usize {
+        self.state.lock().read_dir_call_count
+    }
+
     async fn simulate_random_delay(&self) {
         self.executor
             .upgrade()
@@ -776,6 +793,10 @@ impl FakeFsEntry {
         matches!(self, Self::File { .. })
     }
 
+    fn is_symlink(&self) -> bool {
+        matches!(self, Self::Symlink { .. })
+    }
+
     fn file_content(&self, path: &Path) -> Result<&String> {
         if let Self::File { content, .. } = self {
             Ok(content)
@@ -1056,8 +1077,8 @@ impl Fs for FakeFs {
         let path = normalize_path(path);
         self.simulate_random_delay().await;
         let state = self.state.lock();
-        if let Some((_, real_path)) = state.try_read_path(&path) {
-            Ok(real_path)
+        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
+            Ok(canonical_path)
         } else {
             Err(anyhow!("path does not exist: {}", path.display()))
         }
@@ -1067,7 +1088,7 @@ impl Fs for FakeFs {
         let path = normalize_path(path);
         self.simulate_random_delay().await;
         let state = self.state.lock();
-        if let Some((entry, _)) = state.try_read_path(&path) {
+        if let Some((entry, _)) = state.try_read_path(&path, true) {
             entry.lock().is_file()
         } else {
             false
@@ -1078,10 +1099,17 @@ impl Fs for FakeFs {
         self.simulate_random_delay().await;
         let path = normalize_path(path);
         let state = self.state.lock();
-        if let Some((entry, real_path)) = state.try_read_path(&path) {
-            let entry = entry.lock();
-            let is_symlink = real_path != path;
+        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
+            let is_symlink = entry.lock().is_symlink();
+            if is_symlink {
+                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
+                    entry = e;
+                } else {
+                    return Ok(None);
+                }
+            }
 
+            let entry = entry.lock();
             Ok(Some(match &*entry {
                 FakeFsEntry::File { inode, mtime, .. } => Metadata {
                     inode: *inode,
@@ -1102,13 +1130,30 @@ impl Fs for FakeFs {
         }
     }
 
+    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
+        self.simulate_random_delay().await;
+        let path = normalize_path(path);
+        let state = self.state.lock();
+        if let Some((entry, _)) = state.try_read_path(&path, false) {
+            let entry = entry.lock();
+            if let FakeFsEntry::Symlink { target } = &*entry {
+                Ok(target.clone())
+            } else {
+                Err(anyhow!("not a symlink: {}", path.display()))
+            }
+        } else {
+            Err(anyhow!("path does not exist: {}", path.display()))
+        }
+    }
+
     async fn read_dir(
         &self,
         path: &Path,
     ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
         self.simulate_random_delay().await;
         let path = normalize_path(path);
-        let state = self.state.lock();
+        let mut state = self.state.lock();
+        state.read_dir_call_count += 1;
         let entry = state.read_path(&path)?;
         let mut entry = entry.lock();
         let children = entry.dir_entries(&path)?;

crates/gpui/src/app.rs πŸ”—

@@ -152,6 +152,29 @@ impl App {
             asset_source,
         ))));
 
+        foreground_platform.on_event(Box::new({
+            let cx = app.0.clone();
+            move |event| {
+                if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
+                    // Allow system menu "cmd-?" shortcut to be overridden
+                    if keystroke.cmd
+                        && !keystroke.shift
+                        && !keystroke.alt
+                        && !keystroke.function
+                        && keystroke.key == "?"
+                    {
+                        if cx
+                            .borrow_mut()
+                            .update_active_window(|cx| cx.dispatch_keystroke(keystroke))
+                            .unwrap_or(false)
+                        {
+                            return true;
+                        }
+                    }
+                }
+                false
+            }
+        }));
         foreground_platform.on_quit(Box::new({
             let cx = app.0.clone();
             move || {

crates/gpui/src/color.rs πŸ”—

@@ -6,15 +6,16 @@ use std::{
 
 use crate::json::ToJson;
 use pathfinder_color::{ColorF, ColorU};
+use schemars::JsonSchema;
 use serde::{
     de::{self, Unexpected},
     Deserialize, Deserializer,
 };
 use serde_json::json;
 
-#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
 #[repr(transparent)]
-pub struct Color(ColorU);
+pub struct Color(#[schemars(with = "String")] ColorU);
 
 impl Color {
     pub fn transparent_black() -> Self {

crates/gpui/src/elements.rs πŸ”—

@@ -41,13 +41,7 @@ use collections::HashMap;
 use core::panic;
 use json::ToJson;
 use smallvec::SmallVec;
-use std::{
-    any::Any,
-    borrow::Cow,
-    marker::PhantomData,
-    mem,
-    ops::{Deref, DerefMut, Range},
-};
+use std::{any::Any, borrow::Cow, mem, ops::Range};
 
 pub trait Element<V: View>: 'static {
     type LayoutState;
@@ -567,90 +561,6 @@ impl<V: View> RootElement<V> {
     }
 }
 
-pub trait Component<V: View>: 'static {
-    fn render(&self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
-}
-
-pub struct ComponentHost<V: View, C: Component<V>> {
-    component: C,
-    view_type: PhantomData<V>,
-}
-
-impl<V: View, C: Component<V>> ComponentHost<V, C> {
-    pub fn new(c: C) -> Self {
-        Self {
-            component: c,
-            view_type: PhantomData,
-        }
-    }
-}
-
-impl<V: View, C: Component<V>> Deref for ComponentHost<V, C> {
-    type Target = C;
-
-    fn deref(&self) -> &Self::Target {
-        &self.component
-    }
-}
-
-impl<V: View, C: Component<V>> DerefMut for ComponentHost<V, C> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.component
-    }
-}
-
-impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
-    type LayoutState = AnyElement<V>;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut LayoutContext<V>,
-    ) -> (Vector2F, AnyElement<V>) {
-        let mut element = self.component.render(view, cx);
-        let size = element.layout(constraint, view, cx);
-        (size, element)
-    }
-
-    fn paint(
-        &mut self,
-        scene: &mut SceneBuilder,
-        bounds: RectF,
-        visible_bounds: RectF,
-        element: &mut AnyElement<V>,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        element.paint(scene, bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        element: &AnyElement<V>,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        element.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        element: &AnyElement<V>,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        element.debug(view, cx)
-    }
-}
-
 pub trait AnyRootElement {
     fn layout(
         &mut self,

crates/gpui/src/elements/container.rs πŸ”—

@@ -12,10 +12,11 @@ use crate::{
     scene::{self, Border, CursorRegion, Quad},
     AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
+use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_json::json;
 
-#[derive(Clone, Copy, Debug, Default, Deserialize)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
 pub struct ContainerStyle {
     #[serde(default)]
     pub margin: Margin,
@@ -332,7 +333,7 @@ impl ToJson for ContainerStyle {
     }
 }
 
-#[derive(Clone, Copy, Debug, Default)]
+#[derive(Clone, Copy, Debug, Default, JsonSchema)]
 pub struct Margin {
     pub top: f32,
     pub left: f32,
@@ -359,7 +360,7 @@ impl ToJson for Margin {
     }
 }
 
-#[derive(Clone, Copy, Debug, Default)]
+#[derive(Clone, Copy, Debug, Default, JsonSchema)]
 pub struct Padding {
     pub top: f32,
     pub left: f32,
@@ -486,9 +487,10 @@ impl ToJson for Padding {
     }
 }
 
-#[derive(Clone, Copy, Debug, Default, Deserialize)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
 pub struct Shadow {
     #[serde(default, deserialize_with = "deserialize_vec2f")]
+    #[schemars(with = "Vec::<f32>")]
     offset: Vector2F,
     #[serde(default)]
     blur: f32,

crates/gpui/src/elements/image.rs πŸ”—

@@ -8,6 +8,7 @@ use crate::{
     scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
     ViewContext,
 };
+use schemars::JsonSchema;
 use serde::Deserialize;
 use std::{ops::Range, sync::Arc};
 
@@ -21,7 +22,7 @@ pub struct Image {
     style: ImageStyle,
 }
 
-#[derive(Copy, Clone, Default, Deserialize)]
+#[derive(Copy, Clone, Default, Deserialize, JsonSchema)]
 pub struct ImageStyle {
     #[serde(default)]
     pub border: Border,

crates/gpui/src/elements/label.rs πŸ”—

@@ -10,6 +10,7 @@ use crate::{
     text_layout::{Line, RunStyle},
     Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
+use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_json::json;
 use smallvec::{smallvec, SmallVec};
@@ -20,7 +21,7 @@ pub struct Label {
     highlight_indices: Vec<usize>,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct LabelStyle {
     pub text: TextStyle,
     pub highlight_text: Option<TextStyle>,
@@ -164,6 +165,7 @@ impl<V: View> Element<V> for Label {
         _: &mut V,
         cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
+        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
         line.paint(
             scene,
             bounds.origin(),

crates/gpui/src/elements/svg.rs πŸ”—

@@ -1,7 +1,5 @@
-use std::{borrow::Cow, ops::Range};
-
-use serde_json::json;
-
+use super::constrain_size_preserving_aspect_ratio;
+use crate::json::ToJson;
 use crate::{
     color::Color,
     geometry::{
@@ -10,6 +8,10 @@ use crate::{
     },
     scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
+use schemars::JsonSchema;
+use serde_derive::Deserialize;
+use serde_json::json;
+use std::{borrow::Cow, ops::Range};
 
 pub struct Svg {
     path: Cow<'static, str>,
@@ -24,6 +26,14 @@ impl Svg {
         }
     }
 
+    pub fn for_style<V: View>(style: SvgStyle) -> impl Element<V> {
+        Self::new(style.asset)
+            .with_color(style.color)
+            .constrained()
+            .with_width(style.dimensions.width)
+            .with_height(style.dimensions.height)
+    }
+
     pub fn with_color(mut self, color: Color) -> Self {
         self.color = color;
         self
@@ -105,9 +115,24 @@ impl<V: View> Element<V> for Svg {
     }
 }
 
-use crate::json::ToJson;
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct SvgStyle {
+    pub color: Color,
+    pub asset: String,
+    pub dimensions: Dimensions,
+}
 
-use super::constrain_size_preserving_aspect_ratio;
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct Dimensions {
+    pub width: f32,
+    pub height: f32,
+}
+
+impl Dimensions {
+    pub fn to_vec(&self) -> Vector2F {
+        vec2f(self.width, self.height)
+    }
+}
 
 fn from_usvg_rect(rect: usvg::Rect) -> RectF {
     RectF::new(

crates/gpui/src/elements/tooltip.rs πŸ”—

@@ -9,6 +9,7 @@ use crate::{
     Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
     ViewContext,
 };
+use schemars::JsonSchema;
 use serde::Deserialize;
 use std::{
     cell::{Cell, RefCell},
@@ -33,7 +34,7 @@ struct TooltipState {
     debounce: RefCell<Option<Task<()>>>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct TooltipStyle {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -42,7 +43,7 @@ pub struct TooltipStyle {
     pub max_text_width: Option<f32>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct KeystrokeStyle {
     #[serde(flatten)]
     container: ContainerStyle,

crates/gpui/src/executor.rs πŸ”—

@@ -7,6 +7,7 @@ use std::{
     fmt::{self, Display},
     marker::PhantomData,
     mem,
+    panic::Location,
     pin::Pin,
     rc::Rc,
     sync::Arc,
@@ -965,10 +966,12 @@ impl<T> Task<T> {
 }
 
 impl<T: 'static, E: 'static + Display> Task<Result<T, E>> {
+    #[track_caller]
     pub fn detach_and_log_err(self, cx: &mut AppContext) {
+        let caller = Location::caller();
         cx.spawn(|_| async move {
             if let Err(err) = self.await {
-                log::error!("{:#}", err);
+                log::error!("{}:{}: {:#}", caller.file(), caller.line(), err);
             }
         })
         .detach();

crates/gpui/src/font_cache.rs πŸ”—

@@ -7,13 +7,14 @@ use crate::{
 use anyhow::{anyhow, Result};
 use ordered_float::OrderedFloat;
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
+use schemars::JsonSchema;
 use std::{
     collections::HashMap,
     ops::{Deref, DerefMut},
     sync::Arc,
 };
 
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
 pub struct FamilyId(usize);
 
 struct Family {

crates/gpui/src/fonts.rs πŸ”—

@@ -16,7 +16,7 @@ use serde::{de, Deserialize, Serialize};
 use serde_json::Value;
 use std::{cell::RefCell, sync::Arc};
 
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
 pub struct FontId(pub usize);
 
 pub type GlyphId = u32;
@@ -59,20 +59,44 @@ pub struct Features {
     pub zero: Option<bool>,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, JsonSchema)]
 pub struct TextStyle {
     pub color: Color,
     pub font_family_name: Arc<str>,
     pub font_family_id: FamilyId,
     pub font_id: FontId,
     pub font_size: f32,
+    #[schemars(with = "PropertiesDef")]
     pub font_properties: Properties,
     pub underline: Underline,
 }
 
-#[derive(Copy, Clone, Debug, Default, PartialEq)]
+#[derive(JsonSchema)]
+#[serde(remote = "Properties")]
+pub struct PropertiesDef {
+    /// The font style, as defined in CSS.
+    pub style: StyleDef,
+    /// The font weight, as defined in CSS.
+    pub weight: f32,
+    /// The font stretchiness, as defined in CSS.
+    pub stretch: f32,
+}
+
+#[derive(JsonSchema)]
+#[schemars(remote = "Style")]
+pub enum StyleDef {
+    /// A face that is neither italic not obliqued.
+    Normal,
+    /// A form that is generally cursive in nature.
+    Italic,
+    /// A typically-sloped version of the regular face.
+    Oblique,
+}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
 pub struct HighlightStyle {
     pub color: Option<Color>,
+    #[schemars(with = "Option::<f32>")]
     pub weight: Option<Weight>,
     pub italic: Option<bool>,
     pub underline: Option<Underline>,
@@ -81,9 +105,10 @@ pub struct HighlightStyle {
 
 impl Eq for HighlightStyle {}
 
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
 pub struct Underline {
     pub color: Option<Color>,
+    #[schemars(with = "f32")]
     pub thickness: OrderedFloat<f32>,
     pub squiggly: bool,
 }

crates/gpui/src/gpui.rs πŸ”—

@@ -26,7 +26,7 @@ pub mod color;
 pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
-pub use gpui_macros::test;
+pub use gpui_macros::{test, Element};
 pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
 
 pub use anyhow;

crates/gpui/src/platform.rs πŸ”—

@@ -25,6 +25,7 @@ use anyhow::{anyhow, bail, Result};
 use async_task::Runnable;
 pub use event::*;
 use postage::oneshot;
+use schemars::JsonSchema;
 use serde::Deserialize;
 use sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
@@ -282,7 +283,7 @@ pub enum PromptLevel {
     Critical,
 }
 
-#[derive(Copy, Clone, Debug, Deserialize)]
+#[derive(Copy, Clone, Debug, Deserialize, JsonSchema)]
 pub enum CursorStyle {
     Arrow,
     ResizeLeftRight,

crates/gpui/src/scene.rs πŸ”—

@@ -3,6 +3,7 @@ mod mouse_region;
 
 #[cfg(debug_assertions)]
 use collections::HashSet;
+use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_json::json;
 use std::{borrow::Cow, sync::Arc};
@@ -99,7 +100,7 @@ pub struct Icon {
     pub color: Color,
 }
 
-#[derive(Clone, Copy, Default, Debug)]
+#[derive(Clone, Copy, Default, Debug, JsonSchema)]
 pub struct Border {
     pub width: f32,
     pub color: Color,

crates/gpui_macros/src/gpui_macros.rs πŸ”—

@@ -3,8 +3,8 @@ use proc_macro2::Ident;
 use quote::{format_ident, quote};
 use std::mem;
 use syn::{
-    parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, FnArg, ItemFn, Lit, Meta,
-    NestedMeta, Type,
+    parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
+    ItemFn, Lit, Meta, NestedMeta, Type,
 };
 
 #[proc_macro_attribute]
@@ -275,3 +275,68 @@ fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
 
     result.map_err(|err| TokenStream::from(err.into_compile_error()))
 }
+
+#[proc_macro_derive(Element)]
+pub fn element_derive(input: TokenStream) -> TokenStream {
+    // Parse the input tokens into a syntax tree
+    let input = parse_macro_input!(input as DeriveInput);
+
+    // The name of the struct/enum
+    let name = input.ident;
+
+    let expanded = quote! {
+        impl<V: gpui::View> gpui::elements::Element<V> for #name {
+            type LayoutState = gpui::elements::AnyElement<V>;
+            type PaintState = ();
+
+            fn layout(
+                &mut self,
+                constraint: gpui::SizeConstraint,
+                view: &mut V,
+                cx: &mut gpui::LayoutContext<V>,
+            ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
+                let mut element = self.render(view, cx);
+                let size = element.layout(constraint, view, cx);
+                (size, element)
+            }
+
+            fn paint(
+                &mut self,
+                scene: &mut gpui::SceneBuilder,
+                bounds: gpui::geometry::rect::RectF,
+                visible_bounds: gpui::geometry::rect::RectF,
+                element: &mut gpui::elements::AnyElement<V>,
+                view: &mut V,
+                cx: &mut gpui::ViewContext<V>,
+            ) {
+                element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+            }
+
+            fn rect_for_text_range(
+                &self,
+                range_utf16: std::ops::Range<usize>,
+                _: gpui::geometry::rect::RectF,
+                _: gpui::geometry::rect::RectF,
+                element: &gpui::elements::AnyElement<V>,
+                _: &(),
+                view: &V,
+                cx: &gpui::ViewContext<V>,
+            ) -> Option<gpui::geometry::rect::RectF> {
+                element.rect_for_text_range(range_utf16, view, cx)
+            }
+
+            fn debug(
+                &self,
+                _: gpui::geometry::rect::RectF,
+                element: &gpui::elements::AnyElement<V>,
+                _: &(),
+                view: &V,
+                cx: &gpui::ViewContext<V>,
+            ) -> serde_json::Value {
+                element.debug(view, cx)
+            }
+        }
+    };
+    // Return generated code
+    TokenStream::from(expanded)
+}

crates/language/src/syntax_map.rs πŸ”—

@@ -11,7 +11,7 @@ use std::{
     cell::RefCell,
     cmp::{self, Ordering, Reverse},
     collections::BinaryHeap,
-    iter,
+    fmt, iter,
     ops::{Deref, DerefMut, Range},
     sync::Arc,
 };
@@ -428,6 +428,8 @@ impl SyntaxSnapshot {
         invalidated_ranges: Vec<Range<usize>>,
         registry: Option<&Arc<LanguageRegistry>>,
     ) {
+        log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
+
         let max_depth = self.layers.summary().max_depth;
         let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
         cursor.next(&text);
@@ -489,6 +491,15 @@ impl SyntaxSnapshot {
                     let Some(layer) = cursor.item() else { break };
 
                     if changed_regions.intersects(&layer, text) {
+                        if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
+                            log::trace!(
+                                "discard layer. language:{}, range:{:?}. changed_regions:{:?}",
+                                language.name(),
+                                LogAnchorRange(&layer.range, text),
+                                LogChangedRegions(&changed_regions, text),
+                            );
+                        }
+
                         changed_regions.insert(
                             ChangedRegion {
                                 depth: layer.depth + 1,
@@ -541,26 +552,24 @@ impl SyntaxSnapshot {
                             .to_ts_point();
                     }
 
-                    if included_ranges.is_empty() {
-                        included_ranges.push(tree_sitter::Range {
-                            start_byte: 0,
-                            end_byte: 0,
-                            start_point: Default::default(),
-                            end_point: Default::default(),
-                        });
-                    }
-
-                    if let Some(SyntaxLayerContent::Parsed { tree: old_tree, .. }) =
-                        old_layer.map(|layer| &layer.content)
+                    if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
+                        old_layer.map(|layer| (&layer.content, layer.range.start))
                     {
+                        log::trace!(
+                            "existing layer. language:{}, start:{:?}, ranges:{:?}",
+                            language.name(),
+                            LogPoint(layer_start.to_point(&text)),
+                            LogIncludedRanges(&old_tree.included_ranges())
+                        );
+
                         if let ParseMode::Combined {
                             mut parent_layer_changed_ranges,
                             ..
                         } = step.mode
                         {
                             for range in &mut parent_layer_changed_ranges {
-                                range.start -= step_start_byte;
-                                range.end -= step_start_byte;
+                                range.start = range.start.saturating_sub(step_start_byte);
+                                range.end = range.end.saturating_sub(step_start_byte);
                             }
 
                             included_ranges = splice_included_ranges(
@@ -570,6 +579,22 @@ impl SyntaxSnapshot {
                             );
                         }
 
+                        if included_ranges.is_empty() {
+                            included_ranges.push(tree_sitter::Range {
+                                start_byte: 0,
+                                end_byte: 0,
+                                start_point: Default::default(),
+                                end_point: Default::default(),
+                            });
+                        }
+
+                        log::trace!(
+                            "update layer. language:{}, start:{:?}, ranges:{:?}",
+                            language.name(),
+                            LogAnchorRange(&step.range, text),
+                            LogIncludedRanges(&included_ranges),
+                        );
+
                         tree = parse_text(
                             grammar,
                             text.as_rope(),
@@ -586,6 +611,22 @@ impl SyntaxSnapshot {
                             }),
                         );
                     } else {
+                        if included_ranges.is_empty() {
+                            included_ranges.push(tree_sitter::Range {
+                                start_byte: 0,
+                                end_byte: 0,
+                                start_point: Default::default(),
+                                end_point: Default::default(),
+                            });
+                        }
+
+                        log::trace!(
+                            "create layer. language:{}, range:{:?}, included_ranges:{:?}",
+                            language.name(),
+                            LogAnchorRange(&step.range, text),
+                            LogIncludedRanges(&included_ranges),
+                        );
+
                         tree = parse_text(
                             grammar,
                             text.as_rope(),
@@ -613,6 +654,7 @@ impl SyntaxSnapshot {
                         get_injections(
                             config,
                             text,
+                            step.range.clone(),
                             tree.root_node_with_offset(
                                 step_start_byte,
                                 step_start_point.to_ts_point(),
@@ -1117,6 +1159,7 @@ fn parse_text(
 fn get_injections(
     config: &InjectionConfig,
     text: &BufferSnapshot,
+    outer_range: Range<Anchor>,
     node: Node,
     language_registry: &Arc<LanguageRegistry>,
     depth: usize,
@@ -1153,16 +1196,17 @@ fn get_injections(
                 continue;
             }
 
-            // Avoid duplicate matches if two changed ranges intersect the same injection.
             let content_range =
                 content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte;
-            if let Some((last_pattern_ix, last_range)) = &prev_match {
-                if mat.pattern_index == *last_pattern_ix && content_range == *last_range {
+
+            // Avoid duplicate matches if two changed ranges intersect the same injection.
+            if let Some((prev_pattern_ix, prev_range)) = &prev_match {
+                if mat.pattern_index == *prev_pattern_ix && content_range == *prev_range {
                     continue;
                 }
             }
-            prev_match = Some((mat.pattern_index, content_range.clone()));
 
+            prev_match = Some((mat.pattern_index, content_range.clone()));
             let combined = config.patterns[mat.pattern_index].combined;
 
             let mut language_name = None;
@@ -1218,11 +1262,10 @@ fn get_injections(
 
     for (language, mut included_ranges) in combined_injection_ranges.drain() {
         included_ranges.sort_unstable();
-        let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte());
         queue.push(ParseStep {
             depth,
             language: ParseStepLanguage::Loaded { language },
-            range,
+            range: outer_range.clone(),
             included_ranges,
             mode: ParseMode::Combined {
                 parent_layer_range: node.start_byte()..node.end_byte(),
@@ -1234,72 +1277,77 @@ fn get_injections(
 
 pub(crate) fn splice_included_ranges(
     mut ranges: Vec<tree_sitter::Range>,
-    changed_ranges: &[Range<usize>],
+    removed_ranges: &[Range<usize>],
     new_ranges: &[tree_sitter::Range],
 ) -> Vec<tree_sitter::Range> {
-    let mut changed_ranges = changed_ranges.into_iter().peekable();
-    let mut new_ranges = new_ranges.into_iter().peekable();
+    let mut removed_ranges = removed_ranges.iter().cloned().peekable();
+    let mut new_ranges = new_ranges.into_iter().cloned().peekable();
     let mut ranges_ix = 0;
     loop {
-        let new_range = new_ranges.peek();
-        let mut changed_range = changed_ranges.peek();
-
-        // Remove ranges that have changed before inserting any new ranges
-        // into those ranges.
-        if let Some((changed, new)) = changed_range.zip(new_range) {
-            if new.end_byte < changed.start {
-                changed_range = None;
-            }
-        }
-
-        if let Some(changed) = changed_range {
-            let mut start_ix = ranges_ix
-                + match ranges[ranges_ix..].binary_search_by_key(&changed.start, |r| r.end_byte) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-            let mut end_ix = ranges_ix
-                + match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) {
-                    Ok(ix) => ix + 1,
-                    Err(ix) => ix,
-                };
+        let next_new_range = new_ranges.peek();
+        let next_removed_range = removed_ranges.peek();
 
-            // If there are empty ranges, then there may be multiple ranges with the same
-            // start or end. Expand the splice to include any adjacent ranges that touch
-            // the changed range.
-            while start_ix > 0 {
-                if ranges[start_ix - 1].end_byte == changed.start {
-                    start_ix -= 1;
-                } else {
-                    break;
-                }
-            }
-            while let Some(range) = ranges.get(end_ix) {
-                if range.start_byte == changed.end {
-                    end_ix += 1;
+        let (remove, insert) = match (next_removed_range, next_new_range) {
+            (None, None) => break,
+            (Some(_), None) => (removed_ranges.next().unwrap(), None),
+            (Some(next_removed_range), Some(next_new_range)) => {
+                if next_removed_range.end < next_new_range.start_byte {
+                    (removed_ranges.next().unwrap(), None)
                 } else {
-                    break;
+                    let mut start = next_new_range.start_byte;
+                    let mut end = next_new_range.end_byte;
+
+                    while let Some(next_removed_range) = removed_ranges.peek() {
+                        if next_removed_range.start > next_new_range.end_byte {
+                            break;
+                        }
+                        let next_removed_range = removed_ranges.next().unwrap();
+                        start = cmp::min(start, next_removed_range.start);
+                        end = cmp::max(end, next_removed_range.end);
+                    }
+
+                    (start..end, Some(new_ranges.next().unwrap()))
                 }
             }
+            (None, Some(next_new_range)) => (
+                next_new_range.start_byte..next_new_range.end_byte,
+                Some(new_ranges.next().unwrap()),
+            ),
+        };
 
-            if end_ix > start_ix {
-                ranges.splice(start_ix..end_ix, []);
+        let mut start_ix = ranges_ix
+            + match ranges[ranges_ix..].binary_search_by_key(&remove.start, |r| r.end_byte) {
+                Ok(ix) => ix,
+                Err(ix) => ix,
+            };
+        let mut end_ix = ranges_ix
+            + match ranges[ranges_ix..].binary_search_by_key(&remove.end, |r| r.start_byte) {
+                Ok(ix) => ix + 1,
+                Err(ix) => ix,
+            };
+
+        // If there are empty ranges, then there may be multiple ranges with the same
+        // start or end. Expand the splice to include any adjacent ranges that touch
+        // the changed range.
+        while start_ix > 0 {
+            if ranges[start_ix - 1].end_byte == remove.start {
+                start_ix -= 1;
+            } else {
+                break;
+            }
+        }
+        while let Some(range) = ranges.get(end_ix) {
+            if range.start_byte == remove.end {
+                end_ix += 1;
+            } else {
+                break;
             }
-            changed_ranges.next();
-            ranges_ix = start_ix;
-        } else if let Some(new_range) = new_range {
-            let ix = ranges_ix
-                + match ranges[ranges_ix..]
-                    .binary_search_by_key(&new_range.start_byte, |r| r.start_byte)
-                {
-                    Ok(ix) | Err(ix) => ix,
-                };
-            ranges.insert(ix, **new_range);
-            new_ranges.next();
-            ranges_ix = ix + 1;
-        } else {
-            break;
         }
+
+        ranges.splice(start_ix..end_ix, insert);
+        ranges_ix = start_ix;
     }
+
     ranges
 }
 
@@ -1628,3 +1676,46 @@ impl ToTreeSitterPoint for Point {
         Point::new(point.row as u32, point.column as u32)
     }
 }
+
+struct LogIncludedRanges<'a>(&'a [tree_sitter::Range]);
+struct LogPoint(Point);
+struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
+struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
+
+impl<'a> fmt::Debug for LogIncludedRanges<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_list()
+            .entries(self.0.iter().map(|range| {
+                let start = range.start_point;
+                let end = range.end_point;
+                (start.row, start.column)..(end.row, end.column)
+            }))
+            .finish()
+    }
+}
+
+impl<'a> fmt::Debug for LogAnchorRange<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let range = self.0.to_point(self.1);
+        (LogPoint(range.start)..LogPoint(range.end)).fmt(f)
+    }
+}
+
+impl<'a> fmt::Debug for LogChangedRegions<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_list()
+            .entries(
+                self.0
+                     .0
+                    .iter()
+                    .map(|region| LogAnchorRange(&region.range, self.1)),
+            )
+            .finish()
+    }
+}
+
+impl fmt::Debug for LogPoint {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (self.0.row, self.0.column).fmt(f)
+    }
+}

crates/language/src/syntax_map/syntax_map_tests.rs πŸ”—

@@ -48,6 +48,13 @@ fn test_splice_included_ranges() {
     let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
     assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
 
+    // does not create overlapping ranges
+    let new_ranges = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]);
+    assert_eq!(
+        new_ranges,
+        &[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
+    );
+
     fn ts_range(range: Range<usize>) -> tree_sitter::Range {
         tree_sitter::Range {
             start_byte: range.start,
@@ -624,6 +631,26 @@ fn test_combined_injections_splitting_some_injections() {
     );
 }
 
+#[gpui::test]
+fn test_combined_injections_editing_after_last_injection() {
+    test_edit_sequence(
+        "ERB",
+        &[
+            r#"
+                <% foo %>
+                <div></div>
+                <% bar %>
+            "#,
+            r#"
+                <% foo %>
+                <div></div>
+                <% bar %>Β«
+                more textΒ»
+            "#,
+        ],
+    );
+}
+
 #[gpui::test]
 fn test_combined_injections_inside_injections() {
     let (_buffer, _syntax_map) = test_edit_sequence(
@@ -974,13 +1001,16 @@ fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap
     mutated_syntax_map.reparse(language.clone(), &buffer);
 
     for (i, marked_string) in steps.into_iter().enumerate() {
-        buffer.edit_via_marked_text(&marked_string.unindent());
+        let marked_string = marked_string.unindent();
+        log::info!("incremental parse {i}: {marked_string:?}");
+        buffer.edit_via_marked_text(&marked_string);
 
         // Reparse the syntax map
         mutated_syntax_map.interpolate(&buffer);
         mutated_syntax_map.reparse(language.clone(), &buffer);
 
         // Create a second syntax map from scratch
+        log::info!("fresh parse {i}: {marked_string:?}");
         let mut reference_syntax_map = SyntaxMap::new();
         reference_syntax_map.set_language_registry(registry.clone());
         reference_syntax_map.reparse(language.clone(), &buffer);
@@ -1133,6 +1163,7 @@ fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
     start..start + text.len()
 }
 
+#[track_caller]
 fn assert_layers_for_range(
     syntax_map: &SyntaxMap,
     buffer: &BufferSnapshot,

crates/language_selector/src/active_buffer_language.rs πŸ”—

@@ -55,7 +55,7 @@ impl View for ActiveBufferLanguage {
 
             MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
                 let theme = &theme::current(cx).workspace.status_bar;
-                let style = theme.active_language.style_for(state, false);
+                let style = theme.active_language.style_for(state);
                 Label::new(active_language_text, style.text.clone())
                     .contained()
                     .with_style(style.container)

crates/language_selector/src/language_selector.rs πŸ”—

@@ -180,7 +180,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
     ) -> AnyElement<Picker<Self>> {
         let theme = theme::current(cx);
         let mat = &self.matches[ix];
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
         let mut label = mat.string.clone();
         if buffer_language_name.as_deref() == Some(mat.string.as_str()) {

crates/language_tools/src/lsp_log.rs πŸ”—

@@ -681,7 +681,7 @@ impl LspLogToolbarItemView {
                     )
                 })
                 .unwrap_or_else(|| "No server selected".into());
-            let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
+            let style = theme.toolbar_dropdown_menu.header.style_for(state);
             Label::new(label, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -722,7 +722,8 @@ impl LspLogToolbarItemView {
                     let style = theme
                         .toolbar_dropdown_menu
                         .item
-                        .style_for(state, logs_selected);
+                        .in_state(logs_selected)
+                        .style_for(state);
                     Label::new(SERVER_LOGS, style.text.clone())
                         .contained()
                         .with_style(style.container)
@@ -739,7 +740,8 @@ impl LspLogToolbarItemView {
                     let style = theme
                         .toolbar_dropdown_menu
                         .item
-                        .style_for(state, rpc_trace_selected);
+                        .in_state(rpc_trace_selected)
+                        .style_for(state);
                     Flex::row()
                         .with_child(
                             Label::new(RPC_MESSAGES, style.text.clone())

crates/language_tools/src/syntax_tree_view.rs πŸ”—

@@ -565,7 +565,7 @@ impl SyntaxTreeToolbarItemView {
     ) -> impl Element<Self> {
         enum ToggleMenu {}
         MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
-            let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
+            let style = theme.toolbar_dropdown_menu.header.style_for(state);
             Flex::row()
                 .with_child(
                     Label::new(active_layer.language.name().to_string(), style.text.clone())
@@ -601,7 +601,8 @@ impl SyntaxTreeToolbarItemView {
             let style = theme
                 .toolbar_dropdown_menu
                 .item
-                .style_for(state, is_selected);
+                .in_state(is_selected)
+                .style_for(state);
             Flex::row()
                 .with_child(
                     Label::new(layer.language.name().to_string(), style.text.clone())

crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift πŸ”—

@@ -8,14 +8,18 @@ class LKRoomDelegate: RoomDelegate {
     var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void
     var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
     var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
+    var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void
+    var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void
     var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
     var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
-    
+
     init(
         data: UnsafeRawPointer,
         onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
         onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
         onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
+        onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
+        onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void,
         onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
         onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void)
     {
@@ -25,6 +29,8 @@ class LKRoomDelegate: RoomDelegate {
         self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack
         self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
         self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack
+        self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack
+        self.onActiveSpeakersChanged = onActiveSpeakersChanged
     }
 
     func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) {
@@ -40,6 +46,17 @@ class LKRoomDelegate: RoomDelegate {
             self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque())
         }
     }
+
+    func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) {
+        if publication.kind == .audio {
+            self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted)
+        }
+    }
+    
+    func room(_ room: Room, didUpdate speakers: [Participant]) {
+        guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return }
+        self.onActiveSpeakersChanged(self.data, speaker_ids)
+    }
     
     func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
         if track.kind == .video {
@@ -89,6 +106,8 @@ public func LKRoomDelegateCreate(
     onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
     onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
     onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
+    onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
+    onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void,
     onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
     onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
 ) -> UnsafeMutableRawPointer {
@@ -97,6 +116,8 @@ public func LKRoomDelegateCreate(
         onDidDisconnect: onDidDisconnect,
         onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack,
         onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack,
+        onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack,
+        onActiveSpeakersChanged: onActiveSpeakerChanged,
         onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack,
         onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack
     )
@@ -169,6 +190,18 @@ public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, partic
     return nil;
 }
 
+@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant")
+public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
+    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
+    
+    for (_, participant) in room.remoteParticipants {
+        if participant.identity == participantId as String {
+            return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray?
+        }
+    }
+    
+    return nil;
+}
 
 @_cdecl("LKRoomVideoTracksForRemoteParticipant")
 public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
@@ -201,19 +234,6 @@ public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer)
     return Unmanaged.passRetained(track).toOpaque()
 }
 
-@_cdecl("LKRemoteAudioTrackStart")
-public func LKRemoteAudioTrackStart(track: UnsafeRawPointer, onStart: @escaping @convention(c) (UnsafeRawPointer, Bool) -> Void, callbackData: UnsafeRawPointer) {
-    let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! RemoteAudioTrack
-
-    track.start().then { success in
-        onStart(callbackData, success)
-    }
-    .catch { _ in
-        onStart(callbackData, false)
-    }
-}
-
-
 @_cdecl("LKVideoRendererCreate")
 public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
     Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
@@ -247,3 +267,43 @@ public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @conven
         callback(data, nil, error.localizedDescription as CFString)
     }
 }
+
+@_cdecl("LKLocalTrackPublicationSetMute")
+public func LKLocalTrackPublicationSetMute(
+    publication: UnsafeRawPointer,
+    muted: Bool,
+    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
+    callback_data: UnsafeRawPointer
+) {
+    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+    
+    if muted {
+        publication.mute().then {
+            on_complete(callback_data, nil)
+        }.catch { error in
+            on_complete(callback_data, error.localizedDescription as CFString)
+        }
+    } else {
+        publication.unmute().then {
+            on_complete(callback_data, nil)
+        }.catch { error in
+            on_complete(callback_data, error.localizedDescription as CFString)
+        }
+    }
+}
+
+@_cdecl("LKRemoteTrackPublicationSetEnabled")
+public func LKRemoteTrackPublicationSetEnabled(
+    publication: UnsafeRawPointer,
+    enabled: Bool,
+    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
+    callback_data: UnsafeRawPointer
+) {
+    let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+
+    publication.set(enabled: enabled).then {
+        on_complete(callback_data, nil)
+    }.catch { error in
+        on_complete(callback_data, error.localizedDescription as CFString)
+    }
+}

crates/live_kit_client/examples/test_app.rs πŸ”—

@@ -74,19 +74,51 @@ fn main() {
                 panic!("unexpected message");
             }
 
+            audio_track_publication.set_mute(true).await.unwrap();
+
+            println!("waiting for mute changed!");
+            if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
+                audio_track_updates.next().await.unwrap()
+            {
+                let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
+                assert_eq!(remote_tracks[0].sid(), track_id);
+                assert_eq!(muted, true);
+            } else {
+                panic!("unexpected message");
+            }
+
+            audio_track_publication.set_mute(false).await.unwrap();
+
+            if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
+                audio_track_updates.next().await.unwrap()
+            {
+                let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
+                assert_eq!(remote_tracks[0].sid(), track_id);
+                assert_eq!(muted, false);
+            } else {
+                panic!("unexpected message");
+            }
+
             println!("Pausing for 5 seconds to test audio, make some noise!");
             let timer = cx.background().timer(Duration::from_secs(5));
             timer.await;
-
             let remote_audio_track = room_b
                 .remote_audio_tracks("test-participant-1")
                 .pop()
                 .unwrap();
             room_a.unpublish_track(audio_track_publication);
+
+            // Clear out any active speakers changed messages
+            let mut next = audio_track_updates.next().await.unwrap();
+            while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next {
+                println!("Speakers changed: {:?}", speakers);
+                next = audio_track_updates.next().await.unwrap();
+            }
+
             if let RemoteAudioTrackUpdate::Unsubscribed {
                 publisher_id,
                 track_id,
-            } = audio_track_updates.next().await.unwrap()
+            } = next
             {
                 assert_eq!(publisher_id, "test-participant-1");
                 assert_eq!(remote_audio_track.sid(), track_id);

crates/live_kit_client/src/prod.rs πŸ”—

@@ -32,6 +32,15 @@ extern "C" {
             publisher_id: CFStringRef,
             track_id: CFStringRef,
         ),
+        on_mute_changed_from_remote_audio_track: extern "C" fn(
+            callback_data: *mut c_void,
+            track_id: CFStringRef,
+            muted: bool,
+        ),
+        on_active_speakers_changed: extern "C" fn(
+            callback_data: *mut c_void,
+            participants: CFArrayRef,
+        ),
         on_did_subscribe_to_remote_video_track: extern "C" fn(
             callback_data: *mut c_void,
             publisher_id: CFStringRef,
@@ -72,6 +81,11 @@ extern "C" {
         participant_id: CFStringRef,
     ) -> CFArrayRef;
 
+    fn LKRoomAudioTrackPublicationsForRemoteParticipant(
+        room: *const c_void,
+        participant_id: CFStringRef,
+    ) -> CFArrayRef;
+
     fn LKRoomVideoTracksForRemoteParticipant(
         room: *const c_void,
         participant_id: CFStringRef,
@@ -84,12 +98,6 @@ extern "C" {
     ) -> *const c_void;
 
     fn LKRemoteAudioTrackGetSid(track: *const c_void) -> CFStringRef;
-    // fn LKRemoteAudioTrackStart(
-    //     track: *const c_void,
-    //     callback: extern "C" fn(*mut c_void, bool),
-    //     callback_data: *mut c_void
-    // );
-
     fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
     fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
 
@@ -103,6 +111,20 @@ extern "C" {
     );
     fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
     fn LKLocalAudioTrackCreateTrack() -> *const c_void;
+
+    fn LKLocalTrackPublicationSetMute(
+        publication: *const c_void,
+        muted: bool,
+        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
+        callback_data: *mut c_void,
+    );
+
+    fn LKRemoteTrackPublicationSetEnabled(
+        publication: *const c_void,
+        enabled: bool,
+        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
+        callback_data: *mut c_void,
+    );
 }
 
 pub type Sid = String;
@@ -206,7 +228,7 @@ impl Room {
             let tx =
                 unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
             if error.is_null() {
-                let _ = tx.send(Ok(LocalTrackPublication(publication)));
+                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
             } else {
                 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
                 let _ = tx.send(Err(anyhow!(error)));
@@ -232,7 +254,7 @@ impl Room {
             let tx =
                 unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
             if error.is_null() {
-                let _ = tx.send(Ok(LocalTrackPublication(publication)));
+                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
             } else {
                 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
                 let _ = tx.send(Err(anyhow!(error)));
@@ -246,7 +268,7 @@ impl Room {
                 Box::into_raw(Box::new(tx)) as *mut c_void,
             );
         }
-        async { rx.await.unwrap().context("error publishing video track") }
+        async { rx.await.unwrap().context("error publishing audio track") }
     }
 
     pub fn unpublish_track(&self, publication: LocalTrackPublication) {
@@ -313,6 +335,31 @@ impl Room {
         }
     }
 
+    pub fn remote_audio_track_publications(
+        &self,
+        participant_id: &str,
+    ) -> Vec<Arc<RemoteTrackPublication>> {
+        unsafe {
+            let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant(
+                self.native_room,
+                CFString::new(participant_id).as_concrete_TypeRef(),
+            );
+
+            if tracks.is_null() {
+                Vec::new()
+            } else {
+                let tracks = CFArray::wrap_under_get_rule(tracks);
+                tracks
+                    .into_iter()
+                    .map(|native_track_publication| {
+                        let native_track_publication = *native_track_publication;
+                        Arc::new(RemoteTrackPublication::new(native_track_publication))
+                    })
+                    .collect()
+            }
+        }
+    }
+
     pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> {
         let (tx, rx) = mpsc::unbounded();
         self.remote_audio_track_subscribers.lock().push(tx);
@@ -343,6 +390,28 @@ impl Room {
         });
     }
 
+    fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) {
+        self.remote_audio_track_subscribers.lock().retain(|tx| {
+            tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged {
+                track_id: track_id.clone(),
+                muted,
+            })
+            .is_ok()
+        });
+    }
+
+    // A vec of publisher IDs
+    fn active_speakers_changed(&self, speakers: Vec<String>) {
+        self.remote_audio_track_subscribers
+            .lock()
+            .retain(move |tx| {
+                tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged {
+                    speakers: speakers.clone(),
+                })
+                .is_ok()
+            });
+    }
+
     fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
         let track = Arc::new(track);
         self.remote_video_track_subscribers.lock().retain(|tx| {
@@ -407,6 +476,8 @@ impl RoomDelegate {
                 Self::on_did_disconnect,
                 Self::on_did_subscribe_to_remote_audio_track,
                 Self::on_did_unsubscribe_from_remote_audio_track,
+                Self::on_mute_change_from_remote_audio_track,
+                Self::on_active_speakers_changed,
                 Self::on_did_subscribe_to_remote_video_track,
                 Self::on_did_unsubscribe_from_remote_video_track,
             )
@@ -455,6 +526,42 @@ impl RoomDelegate {
         let _ = Weak::into_raw(room);
     }
 
+    extern "C" fn on_mute_change_from_remote_audio_track(
+        room: *mut c_void,
+        track_id: CFStringRef,
+        muted: bool,
+    ) {
+        let room = unsafe { Weak::from_raw(room as *mut Room) };
+        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
+        if let Some(room) = room.upgrade() {
+            room.mute_changed_from_remote_audio_track(track_id, muted);
+        }
+        let _ = Weak::into_raw(room);
+    }
+
+    extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
+        if participants.is_null() {
+            return;
+        }
+
+        let room = unsafe { Weak::from_raw(room as *mut Room) };
+        let speakers = unsafe {
+            CFArray::wrap_under_get_rule(participants)
+                .into_iter()
+                .map(
+                    |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
+                        CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
+                    },
+                )
+                .collect()
+        };
+
+        if let Some(room) = room.upgrade() {
+            room.active_speakers_changed(speakers);
+        }
+        let _ = Weak::into_raw(room);
+    }
+
     extern "C" fn on_did_subscribe_to_remote_video_track(
         room: *mut c_void,
         publisher_id: CFStringRef,
@@ -525,12 +632,88 @@ impl Drop for LocalVideoTrack {
 
 pub struct LocalTrackPublication(*const c_void);
 
+impl LocalTrackPublication {
+    pub fn new(native_track_publication: *const c_void) -> Self {
+        unsafe {
+            CFRetain(native_track_publication);
+        }
+        Self(native_track_publication)
+    }
+
+    pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
+        let (tx, rx) = futures::channel::oneshot::channel();
+
+        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                tx.send(Ok(())).ok();
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                tx.send(Err(anyhow!(error))).ok();
+            }
+        }
+
+        unsafe {
+            LKLocalTrackPublicationSetMute(
+                self.0,
+                muted,
+                complete_callback,
+                Box::into_raw(Box::new(tx)) as *mut c_void,
+            )
+        }
+
+        async move { rx.await.unwrap() }
+    }
+}
+
 impl Drop for LocalTrackPublication {
     fn drop(&mut self) {
         unsafe { CFRelease(self.0) }
     }
 }
 
+pub struct RemoteTrackPublication(*const c_void);
+
+impl RemoteTrackPublication {
+    pub fn new(native_track_publication: *const c_void) -> Self {
+        unsafe {
+            CFRetain(native_track_publication);
+        }
+        Self(native_track_publication)
+    }
+
+    pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
+        let (tx, rx) = futures::channel::oneshot::channel();
+
+        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                tx.send(Ok(())).ok();
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                tx.send(Err(anyhow!(error))).ok();
+            }
+        }
+
+        unsafe {
+            LKRemoteTrackPublicationSetEnabled(
+                self.0,
+                enabled,
+                complete_callback,
+                Box::into_raw(Box::new(tx)) as *mut c_void,
+            )
+        }
+
+        async move { rx.await.unwrap() }
+    }
+}
+
+impl Drop for RemoteTrackPublication {
+    fn drop(&mut self) {
+        unsafe { CFRelease(self.0) }
+    }
+}
+
 #[derive(Debug)]
 pub struct RemoteAudioTrack {
     _native_track: *const c_void,
@@ -557,6 +740,14 @@ impl RemoteAudioTrack {
     pub fn publisher_id(&self) -> &str {
         &self.publisher_id
     }
+
+    pub fn enable(&self) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
+
+    pub fn disable(&self) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
 }
 
 #[derive(Debug)]
@@ -639,6 +830,8 @@ pub enum RemoteVideoTrackUpdate {
 }
 
 pub enum RemoteAudioTrackUpdate {
+    ActiveSpeakersChanged { speakers: Vec<Sid> },
+    MuteChanged { track_id: Sid, muted: bool },
     Subscribed(Arc<RemoteAudioTrack>),
     Unsubscribed { publisher_id: Sid, track_id: Sid },
 }

crates/live_kit_client/src/test.rs πŸ”—

@@ -410,6 +410,23 @@ impl Room {
             .collect()
     }
 
+    pub fn remote_audio_track_publications(
+        &self,
+        publisher_id: &str,
+    ) -> Vec<Arc<RemoteTrackPublication>> {
+        if !self.is_connected() {
+            return Vec::new();
+        }
+
+        self.test_server()
+            .audio_tracks(self.token())
+            .unwrap()
+            .into_iter()
+            .filter(|track| track.publisher_id() == publisher_id)
+            .map(|_track| Arc::new(RemoteTrackPublication {}))
+            .collect()
+    }
+
     pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
         if !self.is_connected() {
             return Vec::new();
@@ -475,6 +492,20 @@ impl Drop for Room {
 
 pub struct LocalTrackPublication;
 
+impl LocalTrackPublication {
+    pub fn set_mute(&self, _mute: bool) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
+}
+
+pub struct RemoteTrackPublication;
+
+impl RemoteTrackPublication {
+    pub fn set_enabled(&self, _enabled: bool) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
+}
+
 #[derive(Clone)]
 pub struct LocalVideoTrack {
     frames_rx: async_broadcast::Receiver<Frame>,
@@ -517,6 +548,7 @@ impl RemoteVideoTrack {
     }
 }
 
+#[derive(Debug)]
 pub struct RemoteAudioTrack {
     sid: Sid,
     publisher_id: Sid,
@@ -530,6 +562,14 @@ impl RemoteAudioTrack {
     pub fn publisher_id(&self) -> &str {
         &self.publisher_id
     }
+
+    pub fn enable(&self) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
+
+    pub fn disable(&self) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
 }
 
 #[derive(Clone)]
@@ -540,6 +580,8 @@ pub enum RemoteVideoTrackUpdate {
 
 #[derive(Clone)]
 pub enum RemoteAudioTrackUpdate {
+    ActiveSpeakersChanged { speakers: Vec<Sid> },
+    MuteChanged { track_id: Sid, muted: bool },
     Subscribed(Arc<RemoteAudioTrack>),
     Unsubscribed { publisher_id: Sid, track_id: Sid },
 }

crates/lsp/src/lsp.rs πŸ”—

@@ -34,7 +34,7 @@ const JSON_RPC_VERSION: &str = "2.0";
 const CONTENT_LEN_HEADER: &str = "Content-Length: ";
 
 type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
-type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
+type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
 type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
 
 #[derive(Debug, Clone, Deserialize)]
@@ -309,9 +309,9 @@ impl LanguageServer {
                     if let Some(error) = error {
                         handler(Err(error));
                     } else if let Some(result) = result {
-                        handler(Ok(result.get()));
+                        handler(Ok(result.get().into()));
                     } else {
-                        handler(Ok("null"));
+                        handler(Ok("null".into()));
                     }
                 }
             } else {
@@ -464,11 +464,13 @@ impl LanguageServer {
             let response_handlers = self.response_handlers.clone();
             let next_id = AtomicUsize::new(self.next_id.load(SeqCst));
             let outbound_tx = self.outbound_tx.clone();
+            let executor = self.executor.clone();
             let mut output_done = self.output_done_rx.lock().take().unwrap();
             let shutdown_request = Self::request_internal::<request::Shutdown>(
                 &next_id,
                 &response_handlers,
                 &outbound_tx,
+                &executor,
                 (),
             );
             let exit = Self::notify_internal::<notification::Exit>(&outbound_tx, ());
@@ -665,6 +667,7 @@ impl LanguageServer {
             &self.next_id,
             &self.response_handlers,
             &self.outbound_tx,
+            &self.executor,
             params,
         )
     }
@@ -673,6 +676,7 @@ impl LanguageServer {
         next_id: &AtomicUsize,
         response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
         outbound_tx: &channel::Sender<String>,
+        executor: &Arc<executor::Background>,
         params: T::Params,
     ) -> impl 'static + Future<Output = Result<T::Result>>
     where
@@ -693,15 +697,20 @@ impl LanguageServer {
             .as_mut()
             .ok_or_else(|| anyhow!("server shut down"))
             .map(|handlers| {
+                let executor = executor.clone();
                 handlers.insert(
                     id,
                     Box::new(move |result| {
-                        let response = match result {
-                            Ok(response) => serde_json::from_str(response)
-                                .context("failed to deserialize response"),
-                            Err(error) => Err(anyhow!("{}", error.message)),
-                        };
-                        let _ = tx.send(response);
+                        executor
+                            .spawn(async move {
+                                let response = match result {
+                                    Ok(response) => serde_json::from_str(&response)
+                                        .context("failed to deserialize response"),
+                                    Err(error) => Err(anyhow!("{}", error.message)),
+                                };
+                                let _ = tx.send(response);
+                            })
+                            .detach();
                     }),
                 );
             });

crates/outline/src/outline.rs πŸ”—

@@ -204,7 +204,7 @@ impl PickerDelegate for OutlineViewDelegate {
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
         let theme = theme::current(cx);
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         let string_match = &self.matches[ix];
         let outline_item = &self.outline.items[string_match.candidate_id];
 

crates/picker/src/picker.rs πŸ”—

@@ -45,6 +45,12 @@ pub trait PickerDelegate: Sized + 'static {
     fn center_selection_after_match_updates(&self) -> bool {
         false
     }
+    fn render_header(&self, _cx: &AppContext) -> Option<AnyElement<Picker<Self>>> {
+        None
+    }
+    fn render_footer(&self, _cx: &AppContext) -> Option<AnyElement<Picker<Self>>> {
+        None
+    }
 }
 
 impl<D: PickerDelegate> Entity for Picker<D> {
@@ -77,6 +83,7 @@ impl<D: PickerDelegate> View for Picker<D> {
                     .contained()
                     .with_style(editor_style),
             )
+            .with_children(self.delegate.render_header(cx))
             .with_children(if match_count == 0 {
                 if query.is_empty() {
                     None
@@ -118,6 +125,7 @@ impl<D: PickerDelegate> View for Picker<D> {
                     .into_any(),
                 )
             })
+            .with_children(self.delegate.render_footer(cx))
             .contained()
             .with_style(container_style)
             .constrained()

crates/project/src/project.rs πŸ”—

@@ -64,7 +64,7 @@ use std::{
     mem,
     num::NonZeroU32,
     ops::Range,
-    path::{Component, Path, PathBuf},
+    path::{self, Component, Path, PathBuf},
     process::Stdio,
     rc::Rc,
     str,
@@ -481,6 +481,7 @@ impl Project {
         client.add_model_request_handler(Self::handle_rename_project_entry);
         client.add_model_request_handler(Self::handle_copy_project_entry);
         client.add_model_request_handler(Self::handle_delete_project_entry);
+        client.add_model_request_handler(Self::handle_expand_project_entry);
         client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
         client.add_model_request_handler(Self::handle_apply_code_action);
         client.add_model_request_handler(Self::handle_on_type_formatting);
@@ -1075,6 +1076,40 @@ impl Project {
         }
     }
 
+    pub fn expand_entry(
+        &mut self,
+        worktree_id: WorktreeId,
+        entry_id: ProjectEntryId,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<Task<Result<()>>> {
+        let worktree = self.worktree_for_id(worktree_id, cx)?;
+        if self.is_local() {
+            worktree.update(cx, |worktree, cx| {
+                worktree.as_local_mut().unwrap().expand_entry(entry_id, cx)
+            })
+        } else {
+            let worktree = worktree.downgrade();
+            let request = self.client.request(proto::ExpandProjectEntry {
+                project_id: self.remote_id().unwrap(),
+                entry_id: entry_id.to_proto(),
+            });
+            Some(cx.spawn_weak(|_, mut cx| async move {
+                let response = request.await?;
+                if let Some(worktree) = worktree.upgrade(&cx) {
+                    worktree
+                        .update(&mut cx, |worktree, _| {
+                            worktree
+                                .as_remote_mut()
+                                .unwrap()
+                                .wait_for_snapshot(response.worktree_scan_id as usize)
+                        })
+                        .await?;
+                }
+                Ok(())
+            }))
+        }
+    }
+
     pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
         if self.client_state.is_some() {
             return Err(anyhow!("project was already shared"));
@@ -3305,23 +3340,44 @@ impl Project {
             for watcher in params.watchers {
                 for worktree in &self.worktrees {
                     if let Some(worktree) = worktree.upgrade(cx) {
-                        let worktree = worktree.read(cx);
-                        if let Some(abs_path) = worktree.abs_path().to_str() {
-                            if let Some(suffix) = match &watcher.glob_pattern {
-                                lsp::GlobPattern::String(s) => s,
-                                lsp::GlobPattern::Relative(rp) => &rp.pattern,
-                            }
-                            .strip_prefix(abs_path)
-                            .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
-                            {
-                                if let Some(glob) = Glob::new(suffix).log_err() {
-                                    builders
-                                        .entry(worktree.id())
-                                        .or_insert_with(|| GlobSetBuilder::new())
-                                        .add(glob);
+                        let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
+                            if let Some(abs_path) = tree.abs_path().to_str() {
+                                let relative_glob_pattern = match &watcher.glob_pattern {
+                                    lsp::GlobPattern::String(s) => s
+                                        .strip_prefix(abs_path)
+                                        .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)),
+                                    lsp::GlobPattern::Relative(rp) => {
+                                        let base_uri = match &rp.base_uri {
+                                            lsp::OneOf::Left(workspace_folder) => {
+                                                &workspace_folder.uri
+                                            }
+                                            lsp::OneOf::Right(base_uri) => base_uri,
+                                        };
+                                        base_uri.to_file_path().ok().and_then(|file_path| {
+                                            (file_path.to_str() == Some(abs_path))
+                                                .then_some(rp.pattern.as_str())
+                                        })
+                                    }
+                                };
+                                if let Some(relative_glob_pattern) = relative_glob_pattern {
+                                    let literal_prefix =
+                                        glob_literal_prefix(&relative_glob_pattern);
+                                    tree.as_local_mut()
+                                        .unwrap()
+                                        .add_path_prefix_to_scan(Path::new(literal_prefix).into());
+                                    if let Some(glob) = Glob::new(relative_glob_pattern).log_err() {
+                                        builders
+                                            .entry(tree.id())
+                                            .or_insert_with(|| GlobSetBuilder::new())
+                                            .add(glob);
+                                    }
+                                    return true;
                                 }
-                                break;
                             }
+                            false
+                        });
+                        if glob_is_inside_worktree {
+                            break;
                         }
                     }
                 }
@@ -5947,6 +6003,29 @@ impl Project {
         })
     }
 
+    async fn handle_expand_project_entry(
+        this: ModelHandle<Self>,
+        envelope: TypedEnvelope<proto::ExpandProjectEntry>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::ExpandProjectEntryResponse> {
+        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
+        let worktree = this
+            .read_with(&cx, |this, cx| this.worktree_for_entry(entry_id, cx))
+            .ok_or_else(|| anyhow!("invalid request"))?;
+        worktree
+            .update(&mut cx, |worktree, cx| {
+                worktree
+                    .as_local_mut()
+                    .unwrap()
+                    .expand_entry(entry_id, cx)
+                    .ok_or_else(|| anyhow!("invalid entry"))
+            })?
+            .await?;
+        let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id()) as u64;
+        Ok(proto::ExpandProjectEntryResponse { worktree_scan_id })
+    }
+
     async fn handle_update_diagnostic_summary(
         this: ModelHandle<Self>,
         envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
@@ -7289,6 +7368,22 @@ impl Project {
     }
 }
 
+fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str {
+    let mut literal_end = 0;
+    for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
+        if part.contains(&['*', '?', '{', '}']) {
+            break;
+        } else {
+            if i > 0 {
+                // Acount for separator prior to this part
+                literal_end += path::MAIN_SEPARATOR.len_utf8();
+            }
+            literal_end += part.len();
+        }
+    }
+    &glob[..literal_end]
+}
+
 impl WorktreeHandle {
     pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
         match self {

crates/project/src/project_tests.rs πŸ”—

@@ -535,8 +535,28 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     fs.insert_tree(
         "/the-root",
         json!({
-            "a.rs": "",
-            "b.rs": "",
+            ".gitignore": "target\n",
+            "src": {
+                "a.rs": "",
+                "b.rs": "",
+            },
+            "target": {
+                "x": {
+                    "out": {
+                        "x.rs": ""
+                    }
+                },
+                "y": {
+                    "out": {
+                        "y.rs": "",
+                    }
+                },
+                "z": {
+                    "out": {
+                        "z.rs": ""
+                    }
+                }
+            }
         }),
     )
     .await;
@@ -550,11 +570,32 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     // Start the language server by opening a buffer with a compatible file extension.
     let _buffer = project
         .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/a.rs", cx)
+            project.open_local_buffer("/the-root/src/a.rs", cx)
         })
         .await
         .unwrap();
 
+    // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
+    project.read_with(cx, |project, cx| {
+        let worktree = project.worktrees(cx).next().unwrap();
+        assert_eq!(
+            worktree
+                .read(cx)
+                .snapshot()
+                .entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
+                .collect::<Vec<_>>(),
+            &[
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("src"), false),
+                (Path::new("src/a.rs"), false),
+                (Path::new("src/b.rs"), false),
+                (Path::new("target"), true),
+            ]
+        );
+    });
+
     // Keep track of the FS events reported to the language server.
     let fake_server = fake_servers.next().await.unwrap();
     let file_changes = Arc::new(Mutex::new(Vec::new()));
@@ -565,12 +606,20 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
                 method: "workspace/didChangeWatchedFiles".to_string(),
                 register_options: serde_json::to_value(
                     lsp::DidChangeWatchedFilesRegistrationOptions {
-                        watchers: vec![lsp::FileSystemWatcher {
-                            glob_pattern: lsp::GlobPattern::String(
-                                "/the-root/*.{rs,c}".to_string(),
-                            ),
-                            kind: None,
-                        }],
+                        watchers: vec![
+                            lsp::FileSystemWatcher {
+                                glob_pattern: lsp::GlobPattern::String(
+                                    "/the-root/src/*.{rs,c}".to_string(),
+                                ),
+                                kind: None,
+                            },
+                            lsp::FileSystemWatcher {
+                                glob_pattern: lsp::GlobPattern::String(
+                                    "/the-root/target/y/**/*.rs".to_string(),
+                                ),
+                                kind: None,
+                            },
+                        ],
                     },
                 )
                 .ok(),
@@ -588,17 +637,50 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     });
 
     cx.foreground().run_until_parked();
-    assert_eq!(file_changes.lock().len(), 0);
+    assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
+
+    // Now the language server has asked us to watch an ignored directory path,
+    // so we recursively load it.
+    project.read_with(cx, |project, cx| {
+        let worktree = project.worktrees(cx).next().unwrap();
+        assert_eq!(
+            worktree
+                .read(cx)
+                .snapshot()
+                .entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
+                .collect::<Vec<_>>(),
+            &[
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("src"), false),
+                (Path::new("src/a.rs"), false),
+                (Path::new("src/b.rs"), false),
+                (Path::new("target"), true),
+                (Path::new("target/x"), true),
+                (Path::new("target/y"), true),
+                (Path::new("target/y/out"), true),
+                (Path::new("target/y/out/y.rs"), true),
+                (Path::new("target/z"), true),
+            ]
+        );
+    });
 
     // Perform some file system mutations, two of which match the watched patterns,
     // and one of which does not.
-    fs.create_file("/the-root/c.rs".as_ref(), Default::default())
+    fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
+        .await
+        .unwrap();
+    fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
+        .await
+        .unwrap();
+    fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
         .await
         .unwrap();
-    fs.create_file("/the-root/d.txt".as_ref(), Default::default())
+    fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
         .await
         .unwrap();
-    fs.remove_file("/the-root/b.rs".as_ref(), Default::default())
+    fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
         .await
         .unwrap();
 
@@ -608,11 +690,15 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         &*file_changes.lock(),
         &[
             lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/b.rs").unwrap(),
+                uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(),
                 typ: lsp::FileChangeType::DELETED,
             },
             lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/c.rs").unwrap(),
+                uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(),
+                typ: lsp::FileChangeType::CREATED,
+            },
+            lsp::FileEvent {
+                uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
                 typ: lsp::FileChangeType::CREATED,
             },
         ]
@@ -3846,6 +3932,14 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
     );
 }
 
+#[test]
+fn test_glob_literal_prefix() {
+    assert_eq!(glob_literal_prefix("**/*.js"), "");
+    assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
+    assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
+    assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
+}
+
 async fn search(
     project: &ModelHandle<Project>,
     query: SearchQuery,

crates/project/src/worktree.rs πŸ”—

@@ -5,7 +5,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Context, Result};
 use client::{proto, Client};
 use clock::ReplicaId;
-use collections::{HashMap, VecDeque};
+use collections::{HashMap, HashSet, VecDeque};
 use fs::{
     repository::{GitFileStatus, GitRepository, RepoPath},
     Fs, LineEnding,
@@ -67,7 +67,8 @@ pub enum Worktree {
 
 pub struct LocalWorktree {
     snapshot: LocalSnapshot,
-    path_changes_tx: channel::Sender<(Vec<PathBuf>, barrier::Sender)>,
+    scan_requests_tx: channel::Sender<ScanRequest>,
+    path_prefixes_to_scan_tx: channel::Sender<Arc<Path>>,
     is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
     _background_scanner_task: Task<()>,
     share: Option<ShareState>,
@@ -84,6 +85,11 @@ pub struct LocalWorktree {
     visible: bool,
 }
 
+struct ScanRequest {
+    relative_paths: Vec<Arc<Path>>,
+    done: barrier::Sender,
+}
+
 pub struct RemoteWorktree {
     snapshot: Snapshot,
     background_snapshot: Arc<Mutex<Snapshot>>,
@@ -214,6 +220,9 @@ pub struct LocalSnapshot {
 
 struct BackgroundScannerState {
     snapshot: LocalSnapshot,
+    scanned_dirs: HashSet<ProjectEntryId>,
+    path_prefixes_to_scan: HashSet<Arc<Path>>,
+    paths_to_scan: HashSet<Arc<Path>>,
     /// The ids of all of the entries that were removed from the snapshot
     /// as part of the current update. These entry ids may be re-used
     /// if the same inode is discovered at a new path, or if the given
@@ -232,13 +241,6 @@ pub struct LocalRepositoryEntry {
     pub(crate) git_dir_path: Arc<Path>,
 }
 
-impl LocalRepositoryEntry {
-    // Note that this path should be relative to the worktree root.
-    pub(crate) fn in_dot_git(&self, path: &Path) -> bool {
-        path.starts_with(self.git_dir_path.as_ref())
-    }
-}
-
 impl Deref for LocalSnapshot {
     type Target = Snapshot;
 
@@ -330,7 +332,8 @@ impl Worktree {
                 );
             }
 
-            let (path_changes_tx, path_changes_rx) = channel::unbounded();
+            let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
+            let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
             let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
 
             cx.spawn_weak(|this, mut cx| async move {
@@ -370,7 +373,8 @@ impl Worktree {
                         fs,
                         scan_states_tx,
                         background,
-                        path_changes_rx,
+                        scan_requests_rx,
+                        path_prefixes_to_scan_rx,
                     )
                     .run(events)
                     .await;
@@ -381,7 +385,8 @@ impl Worktree {
                 snapshot,
                 is_scanning: watch::channel_with(true),
                 share: None,
-                path_changes_tx,
+                scan_requests_tx,
+                path_prefixes_to_scan_tx,
                 _background_scanner_task: background_scanner_task,
                 diagnostics: Default::default(),
                 diagnostic_summaries: Default::default(),
@@ -867,27 +872,27 @@ impl LocalWorktree {
         path: &Path,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<(File, String, Option<String>)>> {
-        let handle = cx.handle();
         let path = Arc::from(path);
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
-        let snapshot = self.snapshot();
-
-        let mut index_task = None;
-
-        if let Some(repo) = snapshot.repository_for_path(&path) {
-            let repo_path = repo.work_directory.relativize(self, &path).unwrap();
-            if let Some(repo) = self.git_repositories.get(&*repo.work_directory) {
-                let repo = repo.repo_ptr.to_owned();
-                index_task = Some(
-                    cx.background()
-                        .spawn(async move { repo.lock().load_index_text(&repo_path) }),
-                );
-            }
-        }
+        let entry = self.refresh_entry(path.clone(), None, cx);
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(|this, cx| async move {
             let text = fs.load(&abs_path).await?;
+            let entry = entry.await?;
+
+            let mut index_task = None;
+            let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
+            if let Some(repo) = snapshot.repository_for_path(&path) {
+                let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap();
+                if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
+                    let repo = repo.repo_ptr.clone();
+                    index_task = Some(
+                        cx.background()
+                            .spawn(async move { repo.lock().load_index_text(&repo_path) }),
+                    );
+                }
+            }
 
             let diff_base = if let Some(index_task) = index_task {
                 index_task.await
@@ -895,17 +900,10 @@ impl LocalWorktree {
                 None
             };
 
-            // Eagerly populate the snapshot with an updated entry for the loaded file
-            let entry = this
-                .update(&mut cx, |this, cx| {
-                    this.as_local().unwrap().refresh_entry(path, None, cx)
-                })
-                .await?;
-
             Ok((
                 File {
                     entry_id: entry.id,
-                    worktree: handle,
+                    worktree: this,
                     path: entry.path,
                     mtime: entry.mtime,
                     is_local: true,
@@ -1039,14 +1037,10 @@ impl LocalWorktree {
         cx: &mut ModelContext<Worktree>,
     ) -> Option<Task<Result<()>>> {
         let entry = self.entry_for_id(entry_id)?.clone();
-        let abs_path = self.abs_path.clone();
+        let abs_path = self.absolutize(&entry.path);
         let fs = self.fs.clone();
 
         let delete = cx.background().spawn(async move {
-            let mut abs_path = fs.canonicalize(&abs_path).await?;
-            if entry.path.file_name().is_some() {
-                abs_path = abs_path.join(&entry.path);
-            }
             if entry.is_file() {
                 fs.remove_file(&abs_path, Default::default()).await?;
             } else {
@@ -1059,19 +1053,18 @@ impl LocalWorktree {
                 )
                 .await?;
             }
-            anyhow::Ok(abs_path)
+            anyhow::Ok(entry.path)
         });
 
         Some(cx.spawn(|this, mut cx| async move {
-            let abs_path = delete.await?;
-            let (tx, mut rx) = barrier::channel();
+            let path = delete.await?;
             this.update(&mut cx, |this, _| {
                 this.as_local_mut()
                     .unwrap()
-                    .path_changes_tx
-                    .try_send((vec![abs_path], tx))
-            })?;
-            rx.recv().await;
+                    .refresh_entries_for_paths(vec![path])
+            })
+            .recv()
+            .await;
             Ok(())
         }))
     }
@@ -1135,34 +1128,48 @@ impl LocalWorktree {
         }))
     }
 
+    pub fn expand_entry(
+        &mut self,
+        entry_id: ProjectEntryId,
+        cx: &mut ModelContext<Worktree>,
+    ) -> Option<Task<Result<()>>> {
+        let path = self.entry_for_id(entry_id)?.path.clone();
+        let mut refresh = self.refresh_entries_for_paths(vec![path]);
+        Some(cx.background().spawn(async move {
+            refresh.next().await;
+            Ok(())
+        }))
+    }
+
+    pub fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
+        let (tx, rx) = barrier::channel();
+        self.scan_requests_tx
+            .try_send(ScanRequest {
+                relative_paths: paths,
+                done: tx,
+            })
+            .ok();
+        rx
+    }
+
+    pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<Path>) {
+        self.path_prefixes_to_scan_tx.try_send(path_prefix).ok();
+    }
+
     fn refresh_entry(
         &self,
         path: Arc<Path>,
         old_path: Option<Arc<Path>>,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<Entry>> {
-        let fs = self.fs.clone();
-        let abs_root_path = self.abs_path.clone();
-        let path_changes_tx = self.path_changes_tx.clone();
+        let paths = if let Some(old_path) = old_path.as_ref() {
+            vec![old_path.clone(), path.clone()]
+        } else {
+            vec![path.clone()]
+        };
+        let mut refresh = self.refresh_entries_for_paths(paths);
         cx.spawn_weak(move |this, mut cx| async move {
-            let abs_path = fs.canonicalize(&abs_root_path).await?;
-            let mut paths = Vec::with_capacity(2);
-            paths.push(if path.file_name().is_some() {
-                abs_path.join(&path)
-            } else {
-                abs_path.clone()
-            });
-            if let Some(old_path) = old_path {
-                paths.push(if old_path.file_name().is_some() {
-                    abs_path.join(&old_path)
-                } else {
-                    abs_path.clone()
-                });
-            }
-
-            let (tx, mut rx) = barrier::channel();
-            path_changes_tx.try_send((paths, tx))?;
-            rx.recv().await;
+            refresh.recv().await;
             this.upgrade(&cx)
                 .ok_or_else(|| anyhow!("worktree was dropped"))?
                 .update(&mut cx, |this, _| {
@@ -1331,7 +1338,7 @@ impl RemoteWorktree {
         self.completed_scan_id >= scan_id
     }
 
-    fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
+    pub(crate) fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
         let (tx, rx) = oneshot::channel();
         if self.observed_snapshot(scan_id) {
             let _ = tx.send(());
@@ -1568,7 +1575,7 @@ impl Snapshot {
     }
 
     pub fn visible_file_count(&self) -> usize {
-        self.entries_by_path.summary().visible_file_count
+        self.entries_by_path.summary().non_ignored_file_count
     }
 
     fn traverse_from_offset(
@@ -1837,15 +1844,6 @@ impl LocalSnapshot {
         Some((path, self.git_repositories.get(&repo.work_directory_id())?))
     }
 
-    pub(crate) fn repo_for_metadata(
-        &self,
-        path: &Path,
-    ) -> Option<(&ProjectEntryId, &LocalRepositoryEntry)> {
-        self.git_repositories
-            .iter()
-            .find(|(_, repo)| repo.in_dot_git(path))
-    }
-
     fn build_update(
         &self,
         project_id: u64,
@@ -1981,57 +1979,6 @@ impl LocalSnapshot {
         entry
     }
 
-    #[must_use = "Changed paths must be used for diffing later"]
-    fn build_repo(&mut self, parent_path: Arc<Path>, fs: &dyn Fs) -> Option<Vec<Arc<Path>>> {
-        let abs_path = self.abs_path.join(&parent_path);
-        let work_dir: Arc<Path> = parent_path.parent().unwrap().into();
-
-        // Guard against repositories inside the repository metadata
-        if work_dir
-            .components()
-            .find(|component| component.as_os_str() == *DOT_GIT)
-            .is_some()
-        {
-            return None;
-        };
-
-        let work_dir_id = self
-            .entry_for_path(work_dir.clone())
-            .map(|entry| entry.id)?;
-
-        if self.git_repositories.get(&work_dir_id).is_some() {
-            return None;
-        }
-
-        let repo = fs.open_repo(abs_path.as_path())?;
-        let work_directory = RepositoryWorkDirectory(work_dir.clone());
-
-        let repo_lock = repo.lock();
-
-        self.repository_entries.insert(
-            work_directory.clone(),
-            RepositoryEntry {
-                work_directory: work_dir_id.into(),
-                branch: repo_lock.branch_name().map(Into::into),
-            },
-        );
-
-        let changed_paths = self.scan_statuses(repo_lock.deref(), &work_directory);
-
-        drop(repo_lock);
-
-        self.git_repositories.insert(
-            work_dir_id,
-            LocalRepositoryEntry {
-                git_dir_scan_id: 0,
-                repo_ptr: repo,
-                git_dir_path: parent_path.clone(),
-            },
-        );
-
-        Some(changed_paths)
-    }
-
     #[must_use = "Changed paths must be used for diffing later"]
     fn scan_statuses(
         &mut self,
@@ -2098,11 +2045,18 @@ impl LocalSnapshot {
 
         ignore_stack
     }
-}
 
-impl LocalSnapshot {
     #[cfg(test)]
-    pub fn check_invariants(&self) {
+    pub(crate) fn expanded_entries(&self) -> impl Iterator<Item = &Entry> {
+        self.entries_by_path
+            .cursor::<()>()
+            .filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored))
+    }
+
+    #[cfg(test)]
+    pub fn check_invariants(&self, git_state: bool) {
+        use pretty_assertions::assert_eq;
+
         assert_eq!(
             self.entries_by_path
                 .cursor::<()>()
@@ -2122,7 +2076,7 @@ impl LocalSnapshot {
         for entry in self.entries_by_path.cursor::<()>() {
             if entry.is_file() {
                 assert_eq!(files.next().unwrap().inode, entry.inode);
-                if !entry.is_ignored {
+                if !entry.is_ignored && !entry.is_external {
                     assert_eq!(visible_files.next().unwrap().inode, entry.inode);
                 }
             }
@@ -2132,7 +2086,11 @@ impl LocalSnapshot {
         assert!(visible_files.next().is_none());
 
         let mut bfs_paths = Vec::new();
-        let mut stack = vec![Path::new("")];
+        let mut stack = self
+            .root_entry()
+            .map(|e| e.path.as_ref())
+            .into_iter()
+            .collect::<Vec<_>>();
         while let Some(path) = stack.pop() {
             bfs_paths.push(path);
             let ix = stack.len();
@@ -2154,12 +2112,15 @@ impl LocalSnapshot {
             .collect::<Vec<_>>();
         assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
 
-        for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
-            let ignore_parent_path = ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
-            assert!(self.entry_for_path(&ignore_parent_path).is_some());
-            assert!(self
-                .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
-                .is_some());
+        if git_state {
+            for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
+                let ignore_parent_path =
+                    ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
+                assert!(self.entry_for_path(&ignore_parent_path).is_some());
+                assert!(self
+                    .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
+                    .is_some());
+            }
         }
     }
 
@@ -2177,6 +2138,19 @@ impl LocalSnapshot {
 }
 
 impl BackgroundScannerState {
+    fn should_scan_directory(&self, entry: &Entry) -> bool {
+        (!entry.is_external && !entry.is_ignored)
+            || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
+            || self
+                .paths_to_scan
+                .iter()
+                .any(|p| p.starts_with(&entry.path))
+            || self
+                .path_prefixes_to_scan
+                .iter()
+                .any(|p| entry.path.starts_with(p))
+    }
+
     fn reuse_entry_id(&mut self, entry: &mut Entry) {
         if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
             entry.id = removed_entry_id;
@@ -2187,17 +2161,24 @@ impl BackgroundScannerState {
 
     fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
         self.reuse_entry_id(&mut entry);
-        self.snapshot.insert_entry(entry, fs)
+        let entry = self.snapshot.insert_entry(entry, fs);
+        if entry.path.file_name() == Some(&DOT_GIT) {
+            self.build_repository(entry.path.clone(), fs);
+        }
+
+        #[cfg(test)]
+        self.snapshot.check_invariants(false);
+
+        entry
     }
 
-    #[must_use = "Changed paths must be used for diffing later"]
     fn populate_dir(
         &mut self,
-        parent_path: Arc<Path>,
+        parent_path: &Arc<Path>,
         entries: impl IntoIterator<Item = Entry>,
         ignore: Option<Arc<Gitignore>>,
         fs: &dyn Fs,
-    ) -> Option<Vec<Arc<Path>>> {
+    ) {
         let mut parent_entry = if let Some(parent_entry) = self
             .snapshot
             .entries_by_path
@@ -2209,15 +2190,13 @@ impl BackgroundScannerState {
                 "populating a directory {:?} that has been removed",
                 parent_path
             );
-            return None;
+            return;
         };
 
         match parent_entry.kind {
-            EntryKind::PendingDir => {
-                parent_entry.kind = EntryKind::Dir;
-            }
+            EntryKind::PendingDir | EntryKind::UnloadedDir => parent_entry.kind = EntryKind::Dir,
             EntryKind::Dir => {}
-            _ => return None,
+            _ => return,
         }
 
         if let Some(ignore) = ignore {
@@ -2227,11 +2206,16 @@ impl BackgroundScannerState {
                 .insert(abs_parent_path, (ignore, false));
         }
 
+        self.scanned_dirs.insert(parent_entry.id);
         let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
         let mut entries_by_id_edits = Vec::new();
+        let mut dotgit_path = None;
+
+        for entry in entries {
+            if entry.path.file_name() == Some(&DOT_GIT) {
+                dotgit_path = Some(entry.path.clone());
+            }
 
-        for mut entry in entries {
-            self.reuse_entry_id(&mut entry);
             entries_by_id_edits.push(Edit::Insert(PathEntry {
                 id: entry.id,
                 path: entry.path.clone(),
@@ -2246,10 +2230,15 @@ impl BackgroundScannerState {
             .edit(entries_by_path_edits, &());
         self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
 
-        if parent_path.file_name() == Some(&DOT_GIT) {
-            return self.snapshot.build_repo(parent_path, fs);
+        if let Some(dotgit_path) = dotgit_path {
+            self.build_repository(dotgit_path, fs);
         }
-        None
+        if let Err(ix) = self.changed_paths.binary_search(parent_path) {
+            self.changed_paths.insert(ix, parent_path.clone());
+        }
+
+        #[cfg(test)]
+        self.snapshot.check_invariants(false);
     }
 
     fn remove_path(&mut self, path: &Path) {
@@ -2284,6 +2273,137 @@ impl BackgroundScannerState {
                 *needs_update = true;
             }
         }
+
+        #[cfg(test)]
+        self.snapshot.check_invariants(false);
+    }
+
+    fn reload_repositories(&mut self, changed_paths: &[Arc<Path>], fs: &dyn Fs) {
+        let scan_id = self.snapshot.scan_id;
+
+        // Find each of the .git directories that contain any of the given paths.
+        let mut prev_dot_git_dir = None;
+        for changed_path in changed_paths {
+            let Some(dot_git_dir) = changed_path
+                .ancestors()
+                .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT)) else {
+                    continue;
+                };
+
+            // Avoid processing the same repository multiple times, if multiple paths
+            // within it have changed.
+            if prev_dot_git_dir == Some(dot_git_dir) {
+                continue;
+            }
+            prev_dot_git_dir = Some(dot_git_dir);
+
+            // If there is already a repository for this .git directory, reload
+            // the status for all of its files.
+            let repository = self
+                .snapshot
+                .git_repositories
+                .iter()
+                .find_map(|(entry_id, repo)| {
+                    (repo.git_dir_path.as_ref() == dot_git_dir).then(|| (*entry_id, repo.clone()))
+                });
+            match repository {
+                None => {
+                    self.build_repository(dot_git_dir.into(), fs);
+                }
+                Some((entry_id, repository)) => {
+                    if repository.git_dir_scan_id == scan_id {
+                        continue;
+                    }
+                    let Some(work_dir) = self
+                        .snapshot
+                        .entry_for_id(entry_id)
+                        .map(|entry| RepositoryWorkDirectory(entry.path.clone())) else { continue };
+
+                    let repository = repository.repo_ptr.lock();
+                    let branch = repository.branch_name();
+                    repository.reload_index();
+
+                    self.snapshot
+                        .git_repositories
+                        .update(&entry_id, |entry| entry.git_dir_scan_id = scan_id);
+                    self.snapshot
+                        .snapshot
+                        .repository_entries
+                        .update(&work_dir, |entry| entry.branch = branch.map(Into::into));
+
+                    let changed_paths = self.snapshot.scan_statuses(&*repository, &work_dir);
+                    util::extend_sorted(
+                        &mut self.changed_paths,
+                        changed_paths,
+                        usize::MAX,
+                        Ord::cmp,
+                    )
+                }
+            }
+        }
+
+        // Remove any git repositories whose .git entry no longer exists.
+        let mut snapshot = &mut self.snapshot;
+        let mut repositories = mem::take(&mut snapshot.git_repositories);
+        let mut repository_entries = mem::take(&mut snapshot.repository_entries);
+        repositories.retain(|work_directory_id, _| {
+            snapshot
+                .entry_for_id(*work_directory_id)
+                .map_or(false, |entry| {
+                    snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()
+                })
+        });
+        repository_entries.retain(|_, entry| repositories.get(&entry.work_directory.0).is_some());
+        snapshot.git_repositories = repositories;
+        snapshot.repository_entries = repository_entries;
+    }
+
+    fn build_repository(&mut self, dot_git_path: Arc<Path>, fs: &dyn Fs) -> Option<()> {
+        let work_dir_path: Arc<Path> = dot_git_path.parent().unwrap().into();
+
+        // Guard against repositories inside the repository metadata
+        if work_dir_path.iter().any(|component| component == *DOT_GIT) {
+            return None;
+        };
+
+        let work_dir_id = self
+            .snapshot
+            .entry_for_path(work_dir_path.clone())
+            .map(|entry| entry.id)?;
+
+        if self.snapshot.git_repositories.get(&work_dir_id).is_some() {
+            return None;
+        }
+
+        let abs_path = self.snapshot.abs_path.join(&dot_git_path);
+        let repository = fs.open_repo(abs_path.as_path())?;
+        let work_directory = RepositoryWorkDirectory(work_dir_path.clone());
+
+        let repo_lock = repository.lock();
+        self.snapshot.repository_entries.insert(
+            work_directory.clone(),
+            RepositoryEntry {
+                work_directory: work_dir_id.into(),
+                branch: repo_lock.branch_name().map(Into::into),
+            },
+        );
+
+        let changed_paths = self
+            .snapshot
+            .scan_statuses(repo_lock.deref(), &work_directory);
+        drop(repo_lock);
+
+        self.snapshot.git_repositories.insert(
+            work_dir_id,
+            LocalRepositoryEntry {
+                git_dir_scan_id: 0,
+                repo_ptr: repository,
+                git_dir_path: dot_git_path.clone(),
+            },
+        );
+
+        util::extend_sorted(&mut self.changed_paths, changed_paths, usize::MAX, Ord::cmp);
+        Some(())
     }
 }
 
@@ -2570,12 +2690,27 @@ pub struct Entry {
     pub inode: u64,
     pub mtime: SystemTime,
     pub is_symlink: bool,
+
+    /// Whether this entry is ignored by Git.
+    ///
+    /// We only scan ignored entries once the directory is expanded and
+    /// exclude them from searches.
     pub is_ignored: bool,
+
+    /// Whether this entry's canonical path is outside of the worktree.
+    /// This means the entry is only accessible from the worktree root via a
+    /// symlink.
+    ///
+    /// We only scan entries outside of the worktree once the symlinked
+    /// directory is expanded. External entries are treated like gitignored
+    /// entries in that they are not included in searches.
+    pub is_external: bool,
     pub git_status: Option<GitFileStatus>,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum EntryKind {
+    UnloadedDir,
     PendingDir,
     Dir,
     File(CharBag),
@@ -2624,16 +2759,17 @@ impl Entry {
             mtime: metadata.mtime,
             is_symlink: metadata.is_symlink,
             is_ignored: false,
+            is_external: false,
             git_status: None,
         }
     }
 
     pub fn is_dir(&self) -> bool {
-        matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
+        self.kind.is_dir()
     }
 
     pub fn is_file(&self) -> bool {
-        matches!(self.kind, EntryKind::File(_))
+        self.kind.is_file()
     }
 
     pub fn git_status(&self) -> Option<GitFileStatus> {
@@ -2641,19 +2777,40 @@ impl Entry {
     }
 }
 
+impl EntryKind {
+    pub fn is_dir(&self) -> bool {
+        matches!(
+            self,
+            EntryKind::Dir | EntryKind::PendingDir | EntryKind::UnloadedDir
+        )
+    }
+
+    pub fn is_unloaded(&self) -> bool {
+        matches!(self, EntryKind::UnloadedDir)
+    }
+
+    pub fn is_file(&self) -> bool {
+        matches!(self, EntryKind::File(_))
+    }
+}
+
 impl sum_tree::Item for Entry {
     type Summary = EntrySummary;
 
     fn summary(&self) -> Self::Summary {
-        let visible_count = if self.is_ignored { 0 } else { 1 };
+        let non_ignored_count = if self.is_ignored || self.is_external {
+            0
+        } else {
+            1
+        };
         let file_count;
-        let visible_file_count;
+        let non_ignored_file_count;
         if self.is_file() {
             file_count = 1;
-            visible_file_count = visible_count;
+            non_ignored_file_count = non_ignored_count;
         } else {
             file_count = 0;
-            visible_file_count = 0;
+            non_ignored_file_count = 0;
         }
 
         let mut statuses = GitStatuses::default();
@@ -2669,9 +2826,9 @@ impl sum_tree::Item for Entry {
         EntrySummary {
             max_path: self.path.clone(),
             count: 1,
-            visible_count,
+            non_ignored_count,
             file_count,
-            visible_file_count,
+            non_ignored_file_count,
             statuses,
         }
     }
@@ -2689,9 +2846,9 @@ impl sum_tree::KeyedItem for Entry {
 pub struct EntrySummary {
     max_path: Arc<Path>,
     count: usize,
-    visible_count: usize,
+    non_ignored_count: usize,
     file_count: usize,
-    visible_file_count: usize,
+    non_ignored_file_count: usize,
     statuses: GitStatuses,
 }
 
@@ -2700,9 +2857,9 @@ impl Default for EntrySummary {
         Self {
             max_path: Arc::from(Path::new("")),
             count: 0,
-            visible_count: 0,
+            non_ignored_count: 0,
             file_count: 0,
-            visible_file_count: 0,
+            non_ignored_file_count: 0,
             statuses: Default::default(),
         }
     }
@@ -2714,9 +2871,9 @@ impl sum_tree::Summary for EntrySummary {
     fn add_summary(&mut self, rhs: &Self, _: &()) {
         self.max_path = rhs.max_path.clone();
         self.count += rhs.count;
-        self.visible_count += rhs.visible_count;
+        self.non_ignored_count += rhs.non_ignored_count;
         self.file_count += rhs.file_count;
-        self.visible_file_count += rhs.visible_file_count;
+        self.non_ignored_file_count += rhs.non_ignored_file_count;
         self.statuses += rhs.statuses;
     }
 }
@@ -2784,7 +2941,8 @@ struct BackgroundScanner {
     fs: Arc<dyn Fs>,
     status_updates_tx: UnboundedSender<ScanState>,
     executor: Arc<executor::Background>,
-    refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
+    scan_requests_rx: channel::Receiver<ScanRequest>,
+    path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     next_entry_id: Arc<AtomicUsize>,
     phase: BackgroundScannerPhase,
 }
@@ -2803,17 +2961,22 @@ impl BackgroundScanner {
         fs: Arc<dyn Fs>,
         status_updates_tx: UnboundedSender<ScanState>,
         executor: Arc<executor::Background>,
-        refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
+        scan_requests_rx: channel::Receiver<ScanRequest>,
+        path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     ) -> Self {
         Self {
             fs,
             status_updates_tx,
             executor,
-            refresh_requests_rx,
+            scan_requests_rx,
+            path_prefixes_to_scan_rx,
             next_entry_id,
             state: Mutex::new(BackgroundScannerState {
                 prev_snapshot: snapshot.snapshot.clone(),
                 snapshot,
+                scanned_dirs: Default::default(),
+                path_prefixes_to_scan: Default::default(),
+                paths_to_scan: Default::default(),
                 removed_entry_ids: Default::default(),
                 changed_paths: Default::default(),
             }),
@@ -2823,7 +2986,7 @@ impl BackgroundScanner {
 
     async fn run(
         &mut self,
-        mut events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
+        mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
     ) {
         use futures::FutureExt as _;
 
@@ -2868,6 +3031,7 @@ impl BackgroundScanner {
             path: Arc::from(Path::new("")),
             ignore_stack,
             ancestor_inodes: TreeSet::from_ordered_entries(root_inode),
+            is_external: false,
             scan_queue: scan_job_tx.clone(),
         }))
         .unwrap();
@@ -2884,9 +3048,9 @@ impl BackgroundScanner {
         // For these events, update events cannot be as precise, because we didn't
         // have the previous state loaded yet.
         self.phase = BackgroundScannerPhase::EventsReceivedDuringInitialScan;
-        if let Poll::Ready(Some(events)) = futures::poll!(events_rx.next()) {
+        if let Poll::Ready(Some(events)) = futures::poll!(fs_events_rx.next()) {
             let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-            while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
+            while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
                 paths.extend(more_events.into_iter().map(|e| e.path));
             }
             self.process_events(paths).await;
@@ -2898,17 +3062,33 @@ impl BackgroundScanner {
             select_biased! {
                 // Process any path refresh requests from the worktree. Prioritize
                 // these before handling changes reported by the filesystem.
-                request = self.refresh_requests_rx.recv().fuse() => {
-                    let Ok((paths, barrier)) = request else { break };
-                    if !self.process_refresh_request(paths.clone(), barrier).await {
+                request = self.scan_requests_rx.recv().fuse() => {
+                    let Ok(request) = request else { break };
+                    if !self.process_scan_request(request, false).await {
                         return;
                     }
                 }
 
-                events = events_rx.next().fuse() => {
+                path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => {
+                    let Ok(path_prefix) = path_prefix else { break };
+
+                    self.forcibly_load_paths(&[path_prefix.clone()]).await;
+
+                    let abs_path =
+                    {
+                        let mut state = self.state.lock();
+                        state.path_prefixes_to_scan.insert(path_prefix.clone());
+                        state.snapshot.abs_path.join(path_prefix)
+                    };
+                    if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
+                        self.process_events(vec![abs_path]).await;
+                    }
+                }
+
+                events = fs_events_rx.next().fuse() => {
                     let Some(events) = events else { break };
                     let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-                    while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
+                    while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
                         paths.extend(more_events.into_iter().map(|e| e.path));
                     }
                     self.process_events(paths.clone()).await;
@@ -2917,56 +3097,114 @@ impl BackgroundScanner {
         }
     }
 
-    async fn process_refresh_request(&self, paths: Vec<PathBuf>, barrier: barrier::Sender) -> bool {
-        self.reload_entries_for_paths(paths, None).await;
-        self.send_status_update(false, Some(barrier))
+    async fn process_scan_request(&self, request: ScanRequest, scanning: bool) -> bool {
+        log::debug!("rescanning paths {:?}", request.relative_paths);
+
+        let root_path = self.forcibly_load_paths(&request.relative_paths).await;
+        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
+            Ok(path) => path,
+            Err(err) => {
+                log::error!("failed to canonicalize root path: {}", err);
+                return false;
+            }
+        };
+
+        let abs_paths = request
+            .relative_paths
+            .into_iter()
+            .map(|path| {
+                if path.file_name().is_some() {
+                    root_canonical_path.join(path)
+                } else {
+                    root_canonical_path.clone()
+                }
+            })
+            .collect::<Vec<_>>();
+        self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None)
+            .await;
+        self.send_status_update(scanning, Some(request.done))
     }
 
-    async fn process_events(&mut self, paths: Vec<PathBuf>) {
+    async fn process_events(&mut self, abs_paths: Vec<PathBuf>) {
+        log::debug!("received fs events {:?}", abs_paths);
+
+        let root_path = self.state.lock().snapshot.abs_path.clone();
+        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
+            Ok(path) => path,
+            Err(err) => {
+                log::error!("failed to canonicalize root path: {}", err);
+                return;
+            }
+        };
+
         let (scan_job_tx, scan_job_rx) = channel::unbounded();
         let paths = self
-            .reload_entries_for_paths(paths, Some(scan_job_tx.clone()))
+            .reload_entries_for_paths(
+                root_path,
+                root_canonical_path,
+                abs_paths,
+                Some(scan_job_tx.clone()),
+            )
             .await;
         drop(scan_job_tx);
         self.scan_dirs(false, scan_job_rx).await;
 
-        self.update_ignore_statuses().await;
+        let (scan_job_tx, scan_job_rx) = channel::unbounded();
+        self.update_ignore_statuses(scan_job_tx).await;
+        self.scan_dirs(false, scan_job_rx).await;
 
         {
             let mut state = self.state.lock();
-
-            if let Some(paths) = paths {
-                for path in paths {
-                    self.reload_git_repo(&path, &mut *state, self.fs.as_ref());
-                }
+            state.reload_repositories(&paths, self.fs.as_ref());
+            state.snapshot.completed_scan_id = state.snapshot.scan_id;
+            for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
+                state.scanned_dirs.remove(&entry_id);
             }
-
-            let mut snapshot = &mut state.snapshot;
-
-            let mut git_repositories = mem::take(&mut snapshot.git_repositories);
-            git_repositories.retain(|work_directory_id, _| {
-                snapshot
-                    .entry_for_id(*work_directory_id)
-                    .map_or(false, |entry| {
-                        snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()
-                    })
-            });
-            snapshot.git_repositories = git_repositories;
-
-            let mut git_repository_entries = mem::take(&mut snapshot.snapshot.repository_entries);
-            git_repository_entries.retain(|_, entry| {
-                snapshot
-                    .git_repositories
-                    .get(&entry.work_directory.0)
-                    .is_some()
-            });
-            snapshot.snapshot.repository_entries = git_repository_entries;
-            snapshot.completed_scan_id = snapshot.scan_id;
         }
 
         self.send_status_update(false, None);
     }
 
+    async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> Arc<Path> {
+        let root_path;
+        let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
+        {
+            let mut state = self.state.lock();
+            root_path = state.snapshot.abs_path.clone();
+            for path in paths {
+                for ancestor in path.ancestors() {
+                    if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
+                        if entry.kind == EntryKind::UnloadedDir {
+                            let abs_path = root_path.join(ancestor);
+                            let ignore_stack =
+                                state.snapshot.ignore_stack_for_abs_path(&abs_path, true);
+                            let ancestor_inodes =
+                                state.snapshot.ancestor_inodes_for_path(&ancestor);
+                            scan_job_tx
+                                .try_send(ScanJob {
+                                    abs_path: abs_path.into(),
+                                    path: ancestor.into(),
+                                    ignore_stack,
+                                    scan_queue: scan_job_tx.clone(),
+                                    ancestor_inodes,
+                                    is_external: entry.is_external,
+                                })
+                                .unwrap();
+                            state.paths_to_scan.insert(path.clone());
+                            break;
+                        }
+                    }
+                }
+            }
+            drop(scan_job_tx);
+        }
+        while let Some(job) = scan_job_rx.next().await {
+            self.scan_dir(&job).await.log_err();
+        }
+        self.state.lock().paths_to_scan.clear();
+        root_path
+    }
+
     async fn scan_dirs(
         &self,
         enable_progress_updates: bool,

crates/project/src/worktree_tests.rs πŸ”—

@@ -1,6 +1,6 @@
 use crate::{
     worktree::{Event, Snapshot, WorktreeHandle},
-    EntryKind, PathChange, Worktree,
+    Entry, EntryKind, PathChange, Worktree,
 };
 use anyhow::Result;
 use client::Client;
@@ -8,12 +8,14 @@ use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
 use git::GITIGNORE;
 use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
 use parking_lot::Mutex;
+use postage::stream::Stream;
 use pretty_assertions::assert_eq;
 use rand::prelude::*;
 use serde_json::json;
 use std::{
     env,
     fmt::Write,
+    mem,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -34,11 +36,8 @@ async fn test_traversal(cx: &mut TestAppContext) {
     )
     .await;
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
-
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         Path::new("/root"),
         true,
         fs,
@@ -107,11 +106,8 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
     )
     .await;
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
-
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         Path::new("/root"),
         true,
         fs,
@@ -154,7 +150,18 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
                 .collect::<Vec<_>>(),
             vec![Path::new("g"), Path::new("g/h"),]
         );
+    });
+
+    // Expand gitignored directory.
+    tree.read_with(cx, |tree, _| {
+        tree.as_local()
+            .unwrap()
+            .refresh_entries_for_paths(vec![Path::new("i/j").into()])
+    })
+    .recv()
+    .await;
 
+    tree.read_with(cx, |tree, _| {
         assert_eq!(
             tree.descendent_entries(false, false, Path::new("i"))
                 .map(|entry| entry.path.as_ref())
@@ -196,9 +203,8 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
     fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
     fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
 
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         Path::new("/root"),
         true,
         fs.clone(),
@@ -257,40 +263,489 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
 }
 
 #[gpui::test]
-async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
-    // .gitignores are handled explicitly by Zed and do not use the git
-    // machinery that the git_tests module checks
-    let parent_dir = temp_tree(json!({
-        ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
-        "tree": {
-            ".git": {},
-            ".gitignore": "ignored-dir\n",
-            "tracked-dir": {
-                "tracked-file1": "",
-                "ancestor-ignored-file1": "",
+async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/root",
+        json!({
+            "dir1": {
+                "deps": {
+                    // symlinks here
+                },
+                "src": {
+                    "a.rs": "",
+                    "b.rs": "",
+                },
             },
-            "ignored-dir": {
-                "ignored-file1": ""
+            "dir2": {
+                "src": {
+                    "c.rs": "",
+                    "d.rs": "",
+                }
+            },
+            "dir3": {
+                "deps": {},
+                "src": {
+                    "e.rs": "",
+                    "f.rs": "",
+                },
             }
-        }
-    }));
-    let dir = parent_dir.path().join("tree");
+        }),
+    )
+    .await;
 
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    // These symlinks point to directories outside of the worktree's root, dir1.
+    fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
+        .await;
+    fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
+        .await;
 
     let tree = Worktree::local(
-        client,
-        dir.as_path(),
+        build_client(cx),
+        Path::new("/root/dir1"),
         true,
-        Arc::new(RealFs),
+        fs.clone(),
         Default::default(),
         &mut cx.to_async(),
     )
     .await
     .unwrap();
+
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
         .await;
-    tree.flush_fs_events(cx).await;
+
+    let tree_updates = Arc::new(Mutex::new(Vec::new()));
+    tree.update(cx, |_, cx| {
+        let tree_updates = tree_updates.clone();
+        cx.subscribe(&tree, move |_, _, event, _| {
+            if let Event::UpdatedEntries(update) = event {
+                tree_updates.lock().extend(
+                    update
+                        .iter()
+                        .map(|(path, _, change)| (path.clone(), *change)),
+                );
+            }
+        })
+        .detach();
+    });
+
+    // The symlinked directories are not scanned by default.
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_external))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new("deps"), false),
+                (Path::new("deps/dep-dir2"), true),
+                (Path::new("deps/dep-dir3"), true),
+                (Path::new("src"), false),
+                (Path::new("src/a.rs"), false),
+                (Path::new("src/b.rs"), false),
+            ]
+        );
+
+        assert_eq!(
+            tree.entry_for_path("deps/dep-dir2").unwrap().kind,
+            EntryKind::UnloadedDir
+        );
+    });
+
+    // Expand one of the symlinked directories.
+    tree.read_with(cx, |tree, _| {
+        tree.as_local()
+            .unwrap()
+            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
+    })
+    .recv()
+    .await;
+
+    // The expanded directory's contents are loaded. Subdirectories are
+    // not scanned yet.
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_external))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new("deps"), false),
+                (Path::new("deps/dep-dir2"), true),
+                (Path::new("deps/dep-dir3"), true),
+                (Path::new("deps/dep-dir3/deps"), true),
+                (Path::new("deps/dep-dir3/src"), true),
+                (Path::new("src"), false),
+                (Path::new("src/a.rs"), false),
+                (Path::new("src/b.rs"), false),
+            ]
+        );
+    });
+    assert_eq!(
+        mem::take(&mut *tree_updates.lock()),
+        &[
+            (Path::new("deps/dep-dir3").into(), PathChange::Loaded),
+            (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),
+            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)
+        ]
+    );
+
+    // Expand a subdirectory of one of the symlinked directories.
+    tree.read_with(cx, |tree, _| {
+        tree.as_local()
+            .unwrap()
+            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
+    })
+    .recv()
+    .await;
+
+    // The expanded subdirectory's contents are loaded.
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_external))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new("deps"), false),
+                (Path::new("deps/dep-dir2"), true),
+                (Path::new("deps/dep-dir3"), true),
+                (Path::new("deps/dep-dir3/deps"), true),
+                (Path::new("deps/dep-dir3/src"), true),
+                (Path::new("deps/dep-dir3/src/e.rs"), true),
+                (Path::new("deps/dep-dir3/src/f.rs"), true),
+                (Path::new("src"), false),
+                (Path::new("src/a.rs"), false),
+                (Path::new("src/b.rs"), false),
+            ]
+        );
+    });
+
+    assert_eq!(
+        mem::take(&mut *tree_updates.lock()),
+        &[
+            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),
+            (
+                Path::new("deps/dep-dir3/src/e.rs").into(),
+                PathChange::Loaded
+            ),
+            (
+                Path::new("deps/dep-dir3/src/f.rs").into(),
+                PathChange::Loaded
+            )
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_open_gitignored_files(cx: &mut TestAppContext) {
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/root",
+        json!({
+            ".gitignore": "node_modules\n",
+            "one": {
+                "node_modules": {
+                    "a": {
+                        "a1.js": "a1",
+                        "a2.js": "a2",
+                    },
+                    "b": {
+                        "b1.js": "b1",
+                        "b2.js": "b2",
+                    },
+                },
+            },
+            "two": {
+                "x.js": "",
+                "y.js": "",
+            },
+        }),
+    )
+    .await;
+
+    let tree = Worktree::local(
+        build_client(cx),
+        Path::new("/root"),
+        true,
+        fs.clone(),
+        Default::default(),
+        &mut cx.to_async(),
+    )
+    .await
+    .unwrap();
+
+    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+        .await;
+
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("one"), false),
+                (Path::new("one/node_modules"), true),
+                (Path::new("two"), false),
+                (Path::new("two/x.js"), false),
+                (Path::new("two/y.js"), false),
+            ]
+        );
+    });
+
+    // Open a file that is nested inside of a gitignored directory that
+    // has not yet been expanded.
+    let prev_read_dir_count = fs.read_dir_call_count();
+    let buffer = tree
+        .update(cx, |tree, cx| {
+            tree.as_local_mut()
+                .unwrap()
+                .load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
+        })
+        .await
+        .unwrap();
+
+    tree.read_with(cx, |tree, cx| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("one"), false),
+                (Path::new("one/node_modules"), true),
+                (Path::new("one/node_modules/a"), true),
+                (Path::new("one/node_modules/b"), true),
+                (Path::new("one/node_modules/b/b1.js"), true),
+                (Path::new("one/node_modules/b/b2.js"), true),
+                (Path::new("two"), false),
+                (Path::new("two/x.js"), false),
+                (Path::new("two/y.js"), false),
+            ]
+        );
+
+        assert_eq!(
+            buffer.read(cx).file().unwrap().path().as_ref(),
+            Path::new("one/node_modules/b/b1.js")
+        );
+
+        // Only the newly-expanded directories are scanned.
+        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
+    });
+
+    // Open another file in a different subdirectory of the same
+    // gitignored directory.
+    let prev_read_dir_count = fs.read_dir_call_count();
+    let buffer = tree
+        .update(cx, |tree, cx| {
+            tree.as_local_mut()
+                .unwrap()
+                .load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
+        })
+        .await
+        .unwrap();
+
+    tree.read_with(cx, |tree, cx| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
+                .collect::<Vec<_>>(),
+            vec![
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("one"), false),
+                (Path::new("one/node_modules"), true),
+                (Path::new("one/node_modules/a"), true),
+                (Path::new("one/node_modules/a/a1.js"), true),
+                (Path::new("one/node_modules/a/a2.js"), true),
+                (Path::new("one/node_modules/b"), true),
+                (Path::new("one/node_modules/b/b1.js"), true),
+                (Path::new("one/node_modules/b/b2.js"), true),
+                (Path::new("two"), false),
+                (Path::new("two/x.js"), false),
+                (Path::new("two/y.js"), false),
+            ]
+        );
+
+        assert_eq!(
+            buffer.read(cx).file().unwrap().path().as_ref(),
+            Path::new("one/node_modules/a/a2.js")
+        );
+
+        // Only the newly-expanded directory is scanned.
+        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
+    });
+}
+
+#[gpui::test]
+async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/root",
+        json!({
+            ".gitignore": "node_modules\n",
+            "a": {
+                "a.js": "",
+            },
+            "b": {
+                "b.js": "",
+            },
+            "node_modules": {
+                "c": {
+                    "c.js": "",
+                },
+                "d": {
+                    "d.js": "",
+                    "e": {
+                        "e1.js": "",
+                        "e2.js": "",
+                    },
+                    "f": {
+                        "f1.js": "",
+                        "f2.js": "",
+                    }
+                },
+            },
+        }),
+    )
+    .await;
+
+    let tree = Worktree::local(
+        build_client(cx),
+        Path::new("/root"),
+        true,
+        fs.clone(),
+        Default::default(),
+        &mut cx.to_async(),
+    )
+    .await
+    .unwrap();
+
+    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+        .await;
+
+    // Open a file within the gitignored directory, forcing some of its
+    // subdirectories to be read, but not all.
+    let read_dir_count_1 = fs.read_dir_call_count();
+    tree.read_with(cx, |tree, _| {
+        tree.as_local()
+            .unwrap()
+            .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
+    })
+    .recv()
+    .await;
+
+    // Those subdirectories are now loaded.
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|e| (e.path.as_ref(), e.is_ignored))
+                .collect::<Vec<_>>(),
+            &[
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("a"), false),
+                (Path::new("a/a.js"), false),
+                (Path::new("b"), false),
+                (Path::new("b/b.js"), false),
+                (Path::new("node_modules"), true),
+                (Path::new("node_modules/c"), true),
+                (Path::new("node_modules/d"), true),
+                (Path::new("node_modules/d/d.js"), true),
+                (Path::new("node_modules/d/e"), true),
+                (Path::new("node_modules/d/f"), true),
+            ]
+        );
+    });
+    let read_dir_count_2 = fs.read_dir_call_count();
+    assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
+
+    // Update the gitignore so that node_modules is no longer ignored,
+    // but a subdirectory is ignored
+    fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
+        .await
+        .unwrap();
+    cx.foreground().run_until_parked();
+
+    // All of the directories that are no longer ignored are now loaded.
+    tree.read_with(cx, |tree, _| {
+        assert_eq!(
+            tree.entries(true)
+                .map(|e| (e.path.as_ref(), e.is_ignored))
+                .collect::<Vec<_>>(),
+            &[
+                (Path::new(""), false),
+                (Path::new(".gitignore"), false),
+                (Path::new("a"), false),
+                (Path::new("a/a.js"), false),
+                (Path::new("b"), false),
+                (Path::new("b/b.js"), false),
+                // This directory is no longer ignored
+                (Path::new("node_modules"), false),
+                (Path::new("node_modules/c"), false),
+                (Path::new("node_modules/c/c.js"), false),
+                (Path::new("node_modules/d"), false),
+                (Path::new("node_modules/d/d.js"), false),
+                // This subdirectory is now ignored
+                (Path::new("node_modules/d/e"), true),
+                (Path::new("node_modules/d/f"), false),
+                (Path::new("node_modules/d/f/f1.js"), false),
+                (Path::new("node_modules/d/f/f2.js"), false),
+            ]
+        );
+    });
+
+    // Each of the newly-loaded directories is scanned only once.
+    let read_dir_count_3 = fs.read_dir_call_count();
+    assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/root",
+        json!({
+            ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
+            "tree": {
+                ".git": {},
+                ".gitignore": "ignored-dir\n",
+                "tracked-dir": {
+                    "tracked-file1": "",
+                    "ancestor-ignored-file1": "",
+                },
+                "ignored-dir": {
+                    "ignored-file1": ""
+                }
+            }
+        }),
+    )
+    .await;
+
+    let tree = Worktree::local(
+        build_client(cx),
+        "/root/tree".as_ref(),
+        true,
+        fs.clone(),
+        Default::default(),
+        &mut cx.to_async(),
+    )
+    .await
+    .unwrap();
+    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+        .await;
+
+    tree.read_with(cx, |tree, _| {
+        tree.as_local()
+            .unwrap()
+            .refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
+    })
+    .recv()
+    .await;
+
     cx.read(|cx| {
         let tree = tree.read(cx);
         assert!(
@@ -311,10 +766,26 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
         );
     });
 
-    std::fs::write(dir.join("tracked-dir/tracked-file2"), "").unwrap();
-    std::fs::write(dir.join("tracked-dir/ancestor-ignored-file2"), "").unwrap();
-    std::fs::write(dir.join("ignored-dir/ignored-file2"), "").unwrap();
-    tree.flush_fs_events(cx).await;
+    fs.create_file(
+        "/root/tree/tracked-dir/tracked-file2".as_ref(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    fs.create_file(
+        "/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    fs.create_file(
+        "/root/tree/ignored-dir/ignored-file2".as_ref(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+
+    cx.foreground().run_until_parked();
     cx.read(|cx| {
         let tree = tree.read(cx);
         assert!(
@@ -346,10 +817,8 @@ async fn test_write_file(cx: &mut TestAppContext) {
         "ignored-dir": {}
     }));
 
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         dir.path(),
         true,
         Arc::new(RealFs),
@@ -393,8 +862,6 @@ async fn test_write_file(cx: &mut TestAppContext) {
 
 #[gpui::test(iterations = 30)]
 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/root",
@@ -407,7 +874,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
     .await;
 
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         "/root".as_ref(),
         true,
         fs,
@@ -472,9 +939,8 @@ async fn test_random_worktree_operations_during_initial_scan(
     }
     log::info!("generated initial tree");
 
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
     let worktree = Worktree::local(
-        client.clone(),
+        build_client(cx),
         root_dir,
         true,
         fs.clone(),
@@ -506,7 +972,7 @@ async fn test_random_worktree_operations_during_initial_scan(
             .await
             .log_err();
         worktree.read_with(cx, |tree, _| {
-            tree.as_local().unwrap().snapshot().check_invariants()
+            tree.as_local().unwrap().snapshot().check_invariants(true)
         });
 
         if rng.gen_bool(0.6) {
@@ -523,7 +989,7 @@ async fn test_random_worktree_operations_during_initial_scan(
     let final_snapshot = worktree.read_with(cx, |tree, _| {
         let tree = tree.as_local().unwrap();
         let snapshot = tree.snapshot();
-        snapshot.check_invariants();
+        snapshot.check_invariants(true);
         snapshot
     });
 
@@ -562,9 +1028,8 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
     }
     log::info!("generated initial tree");
 
-    let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
     let worktree = Worktree::local(
-        client.clone(),
+        build_client(cx),
         root_dir,
         true,
         fs.clone(),
@@ -627,12 +1092,17 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
     log::info!("quiescing");
     fs.as_fake().flush_events(usize::MAX);
     cx.foreground().run_until_parked();
+
     let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-    snapshot.check_invariants();
+    snapshot.check_invariants(true);
+    let expanded_paths = snapshot
+        .expanded_entries()
+        .map(|e| e.path.clone())
+        .collect::<Vec<_>>();
 
     {
         let new_worktree = Worktree::local(
-            client.clone(),
+            build_client(cx),
             root_dir,
             true,
             fs.clone(),
@@ -644,6 +1114,14 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
         new_worktree
             .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
             .await;
+        new_worktree
+            .update(cx, |tree, _| {
+                tree.as_local_mut()
+                    .unwrap()
+                    .refresh_entries_for_paths(expanded_paths)
+            })
+            .recv()
+            .await;
         let new_snapshot =
             new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
         assert_eq!(
@@ -660,11 +1138,25 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
         }
 
         assert_eq!(
-            prev_snapshot.entries(true).collect::<Vec<_>>(),
-            snapshot.entries(true).collect::<Vec<_>>(),
+            prev_snapshot
+                .entries(true)
+                .map(ignore_pending_dir)
+                .collect::<Vec<_>>(),
+            snapshot
+                .entries(true)
+                .map(ignore_pending_dir)
+                .collect::<Vec<_>>(),
             "wrong updates after snapshot {i}: {updates:#?}",
         );
     }
+
+    fn ignore_pending_dir(entry: &Entry) -> Entry {
+        let mut entry = entry.clone();
+        if entry.kind.is_dir() {
+            entry.kind = EntryKind::Dir
+        }
+        entry
+    }
 }
 
 // The worktree's `UpdatedEntries` event can be used to follow along with
@@ -679,7 +1171,6 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Workt
                     Ok(ix) | Err(ix) => ix,
                 };
                 match change_type {
-                    PathChange::Loaded => entries.insert(ix, entry.unwrap()),
                     PathChange::Added => entries.insert(ix, entry.unwrap()),
                     PathChange::Removed => drop(entries.remove(ix)),
                     PathChange::Updated => {
@@ -688,7 +1179,7 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Workt
                         assert_eq!(existing_entry.path, entry.path);
                         *existing_entry = entry;
                     }
-                    PathChange::AddedOrUpdated => {
+                    PathChange::AddedOrUpdated | PathChange::Loaded => {
                         let entry = entry.unwrap();
                         if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
                             *entries.get_mut(ix).unwrap() = entry;
@@ -947,10 +1438,8 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
     }));
     let root_path = root.path();
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         root_path,
         true,
         Arc::new(RealFs),
@@ -1026,10 +1515,8 @@ async fn test_git_repository_for_path(cx: &mut TestAppContext) {
         },
     }));
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         root.path(),
         true,
         Arc::new(RealFs),
@@ -1150,10 +1637,8 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
 
     }));
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         root.path(),
         true,
         Arc::new(RealFs),
@@ -1357,10 +1842,8 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
         ],
     );
 
-    let http_client = FakeHttpClient::with_404_response();
-    let client = cx.read(|cx| Client::new(http_client, cx));
     let tree = Worktree::local(
-        client,
+        build_client(cx),
         Path::new("/root"),
         true,
         fs.clone(),
@@ -1439,6 +1922,11 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     }
 }
 
+fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
+    let http_client = FakeHttpClient::with_404_response();
+    cx.read(|cx| Client::new(http_client, cx))
+}
+
 #[track_caller]
 fn git_init(path: &Path) -> git2::Repository {
     git2::Repository::init(path).expect("Failed to initialize git repository")

crates/project_panel/src/project_panel.rs πŸ”—

@@ -153,6 +153,7 @@ pub fn init(cx: &mut AppContext) {
     );
 }
 
+#[derive(Debug)]
 pub enum Event {
     OpenedEntry {
         entry_id: ProjectEntryId,
@@ -410,17 +411,23 @@ impl ProjectPanel {
     fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
         if let Some((worktree, entry)) = self.selected_entry(cx) {
             if entry.is_dir() {
+                let worktree_id = worktree.id();
+                let entry_id = entry.id;
                 let expanded_dir_ids =
-                    if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree.id()) {
+                    if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
                         expanded_dir_ids
                     } else {
                         return;
                     };
 
-                match expanded_dir_ids.binary_search(&entry.id) {
+                match expanded_dir_ids.binary_search(&entry_id) {
                     Ok(_) => self.select_next(&SelectNext, cx),
                     Err(ix) => {
-                        expanded_dir_ids.insert(ix, entry.id);
+                        self.project.update(cx, |project, cx| {
+                            project.expand_entry(worktree_id, entry_id, cx);
+                        });
+
+                        expanded_dir_ids.insert(ix, entry_id);
                         self.update_visible_entries(None, cx);
                         cx.notify();
                     }
@@ -431,18 +438,20 @@ impl ProjectPanel {
 
     fn collapse_selected_entry(&mut self, _: &CollapseSelectedEntry, cx: &mut ViewContext<Self>) {
         if let Some((worktree, mut entry)) = self.selected_entry(cx) {
+            let worktree_id = worktree.id();
             let expanded_dir_ids =
-                if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree.id()) {
+                if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
                     expanded_dir_ids
                 } else {
                     return;
                 };
 
             loop {
-                match expanded_dir_ids.binary_search(&entry.id) {
+                let entry_id = entry.id;
+                match expanded_dir_ids.binary_search(&entry_id) {
                     Ok(ix) => {
                         expanded_dir_ids.remove(ix);
-                        self.update_visible_entries(Some((worktree.id(), entry.id)), cx);
+                        self.update_visible_entries(Some((worktree_id, entry_id)), cx);
                         cx.notify();
                         break;
                     }
@@ -463,14 +472,17 @@ impl ProjectPanel {
     fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
         if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) {
             if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
-                match expanded_dir_ids.binary_search(&entry_id) {
-                    Ok(ix) => {
-                        expanded_dir_ids.remove(ix);
-                    }
-                    Err(ix) => {
-                        expanded_dir_ids.insert(ix, entry_id);
+                self.project.update(cx, |project, cx| {
+                    match expanded_dir_ids.binary_search(&entry_id) {
+                        Ok(ix) => {
+                            expanded_dir_ids.remove(ix);
+                        }
+                        Err(ix) => {
+                            project.expand_entry(worktree_id, entry_id, cx);
+                            expanded_dir_ids.insert(ix, entry_id);
+                        }
                     }
-                }
+                });
                 self.update_visible_entries(Some((worktree_id, entry_id)), cx);
                 cx.focus_self();
                 cx.notify();
@@ -938,10 +950,19 @@ impl ProjectPanel {
     }
 
     fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> {
+        let (worktree, entry) = self.selected_entry_handle(cx)?;
+        Some((worktree.read(cx), entry))
+    }
+
+    fn selected_entry_handle<'a>(
+        &self,
+        cx: &'a AppContext,
+    ) -> Option<(ModelHandle<Worktree>, &'a project::Entry)> {
         let selection = self.selection?;
         let project = self.project.read(cx);
-        let worktree = project.worktree_for_id(selection.worktree_id, cx)?.read(cx);
-        Some((worktree, worktree.entry_for_id(selection.entry_id)?))
+        let worktree = project.worktree_for_id(selection.worktree_id, cx)?;
+        let entry = worktree.read(cx).entry_for_id(selection.entry_id)?;
+        Some((worktree, entry))
     }
 
     fn update_visible_entries(
@@ -1002,6 +1023,7 @@ impl ProjectPanel {
                         mtime: entry.mtime,
                         is_symlink: false,
                         is_ignored: false,
+                        is_external: false,
                         git_status: entry.git_status,
                     });
                 }
@@ -1058,29 +1080,31 @@ impl ProjectPanel {
         entry_id: ProjectEntryId,
         cx: &mut ViewContext<Self>,
     ) {
-        let project = self.project.read(cx);
-        if let Some((worktree, expanded_dir_ids)) = project
-            .worktree_for_id(worktree_id, cx)
-            .zip(self.expanded_dir_ids.get_mut(&worktree_id))
-        {
-            let worktree = worktree.read(cx);
+        self.project.update(cx, |project, cx| {
+            if let Some((worktree, expanded_dir_ids)) = project
+                .worktree_for_id(worktree_id, cx)
+                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
+            {
+                project.expand_entry(worktree_id, entry_id, cx);
+                let worktree = worktree.read(cx);
 
-            if let Some(mut entry) = worktree.entry_for_id(entry_id) {
-                loop {
-                    if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) {
-                        expanded_dir_ids.insert(ix, entry.id);
-                    }
+                if let Some(mut entry) = worktree.entry_for_id(entry_id) {
+                    loop {
+                        if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) {
+                            expanded_dir_ids.insert(ix, entry.id);
+                        }
 
-                    if let Some(parent_entry) =
-                        entry.path.parent().and_then(|p| worktree.entry_for_path(p))
-                    {
-                        entry = parent_entry;
-                    } else {
-                        break;
+                        if let Some(parent_entry) =
+                            entry.path.parent().and_then(|p| worktree.entry_for_path(p))
+                        {
+                            entry = parent_entry;
+                        } else {
+                            break;
+                        }
                     }
                 }
             }
-        }
+        });
     }
 
     fn for_each_visible_entry(
@@ -1190,7 +1214,7 @@ impl ProjectPanel {
 
         Flex::row()
             .with_child(
-                if kind == EntryKind::Dir {
+                if kind.is_dir() {
                     if details.is_expanded {
                         Svg::new("icons/chevron_down_8.svg").with_color(style.icon_color)
                     } else {
@@ -1253,7 +1277,10 @@ impl ProjectPanel {
         let show_editor = details.is_editing && !details.is_processing;
 
         MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
-            let mut style = entry_style.style_for(state, details.is_selected).clone();
+            let mut style = entry_style
+                .in_state(details.is_selected)
+                .style_for(state)
+                .clone();
 
             if cx
                 .global::<DragAndDrop<Workspace>>()
@@ -1264,7 +1291,7 @@ impl ProjectPanel {
                     .filter(|destination| details.path.starts_with(destination))
                     .is_some()
             {
-                style = entry_style.active.clone().unwrap();
+                style = entry_style.active_state().default.clone();
             }
 
             let row_container_style = if show_editor {
@@ -1284,7 +1311,7 @@ impl ProjectPanel {
         })
         .on_click(MouseButton::Left, move |event, this, cx| {
             if !show_editor {
-                if kind == EntryKind::Dir {
+                if kind.is_dir() {
                     this.toggle_expanded(entry_id, cx);
                 } else {
                     this.open_entry(entry_id, event.click_count > 1, cx);
@@ -1405,9 +1432,11 @@ impl View for ProjectPanel {
                         let button_style = theme.open_project_button.clone();
                         let context_menu_item_style = theme::current(cx).context_menu.item.clone();
                         move |state, cx| {
-                            let button_style = button_style.style_for(state, false).clone();
-                            let context_menu_item =
-                                context_menu_item_style.style_for(state, true).clone();
+                            let button_style = button_style.style_for(state).clone();
+                            let context_menu_item = context_menu_item_style
+                                .active_state()
+                                .style_for(state)
+                                .clone();
 
                             theme::ui::keystroke_label(
                                 "Open a project",
@@ -2343,7 +2372,7 @@ mod tests {
                 }
 
                 let indent = "    ".repeat(details.depth);
-                let icon = if matches!(details.kind, EntryKind::Dir | EntryKind::PendingDir) {
+                let icon = if details.kind.is_dir() {
                     if details.is_expanded {
                         "v "
                     } else {

crates/project_symbols/src/project_symbols.rs πŸ”—

@@ -196,7 +196,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
     ) -> AnyElement<Picker<Self>> {
         let theme = theme::current(cx);
         let style = &theme.picker.item;
-        let current_style = style.style_for(mouse_state, selected);
+        let current_style = style.in_state(selected).style_for(mouse_state);
 
         let string_match = &self.matches[ix];
         let symbol = &self.symbols[string_match.candidate_id];
@@ -229,7 +229,10 @@ impl PickerDelegate for ProjectSymbolsDelegate {
             .with_child(
                 // Avoid styling the path differently when it is selected, since
                 // the symbol's syntax highlighting doesn't change when selected.
-                Label::new(path.to_string(), style.default.label.clone()),
+                Label::new(
+                    path.to_string(),
+                    style.inactive_state().default.label.clone(),
+                ),
             )
             .contained()
             .with_style(current_style.container)

crates/recent_projects/src/recent_projects.rs πŸ”—

@@ -173,7 +173,7 @@ impl PickerDelegate for RecentProjectsDelegate {
         cx: &gpui::AppContext,
     ) -> AnyElement<Picker<Self>> {
         let theme = theme::current(cx);
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
 
         let string_match = &self.matches[ix];
 

crates/rpc/proto/zed.proto πŸ”—

@@ -63,6 +63,8 @@ message Envelope {
         CopyProjectEntry copy_project_entry = 47;
         DeleteProjectEntry delete_project_entry = 48;
         ProjectEntryResponse project_entry_response = 49;
+        ExpandProjectEntry expand_project_entry = 114;
+        ExpandProjectEntryResponse expand_project_entry_response = 115;
 
         UpdateDiagnosticSummary update_diagnostic_summary = 50;
         StartLanguageServer start_language_server = 51;
@@ -372,6 +374,15 @@ message DeleteProjectEntry {
     uint64 entry_id = 2;
 }
 
+message ExpandProjectEntry {
+    uint64 project_id = 1;
+    uint64 entry_id = 2;
+}
+
+message ExpandProjectEntryResponse {
+    uint64 worktree_scan_id = 1;
+}
+
 message ProjectEntryResponse {
     Entry entry = 1;
     uint64 worktree_scan_id = 2;
@@ -1005,7 +1016,8 @@ message Entry {
     Timestamp mtime = 5;
     bool is_symlink = 6;
     bool is_ignored = 7;
-    optional GitStatus git_status = 8;
+    bool is_external = 8;
+    optional GitStatus git_status = 9;
 }
 
 message RepositoryEntry {

crates/rpc/src/proto.rs πŸ”—

@@ -150,6 +150,7 @@ messages!(
     (DeclineCall, Foreground),
     (DeleteProjectEntry, Foreground),
     (Error, Foreground),
+    (ExpandProjectEntry, Foreground),
     (Follow, Foreground),
     (FollowResponse, Foreground),
     (FormatBuffers, Foreground),
@@ -200,6 +201,7 @@ messages!(
     (Ping, Foreground),
     (PrepareRename, Background),
     (PrepareRenameResponse, Background),
+    (ExpandProjectEntryResponse, Foreground),
     (ProjectEntryResponse, Foreground),
     (RejoinRoom, Foreground),
     (RejoinRoomResponse, Foreground),
@@ -255,6 +257,7 @@ request_messages!(
     (CreateRoom, CreateRoomResponse),
     (DeclineCall, Ack),
     (DeleteProjectEntry, ProjectEntryResponse),
+    (ExpandProjectEntry, ExpandProjectEntryResponse),
     (Follow, FollowResponse),
     (FormatBuffers, FormatBuffersResponse),
     (GetChannelMessages, GetChannelMessagesResponse),
@@ -311,6 +314,7 @@ entity_messages!(
     CreateBufferForPeer,
     CreateProjectEntry,
     DeleteProjectEntry,
+    ExpandProjectEntry,
     Follow,
     FormatBuffers,
     GetCodeActions,

crates/rpc/src/rpc.rs πŸ”—

@@ -6,4 +6,4 @@ pub use conn::Connection;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 58;
+pub const PROTOCOL_VERSION: u32 = 59;

crates/search/src/buffer_search.rs πŸ”—

@@ -259,7 +259,11 @@ impl BufferSearchBar {
         }
     }
 
-    fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+    pub fn is_dismissed(&self) -> bool {
+        self.dismissed
+    }
+
+    pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
         self.dismissed = true;
         for searchable_item in self.seachable_items_with_matches.keys() {
             if let Some(searchable_item) =
@@ -275,7 +279,7 @@ impl BufferSearchBar {
         cx.notify();
     }
 
-    fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
+    pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
         let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
             SearchableItemHandle::boxed_clone(searchable_item.as_ref())
         } else {
@@ -328,7 +332,11 @@ impl BufferSearchBar {
         Some(
             MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
                 let theme = theme::current(cx);
-                let style = theme.search.option_button.style_for(state, is_active);
+                let style = theme
+                    .search
+                    .option_button
+                    .in_state(is_active)
+                    .style_for(state);
                 Label::new(icon, style.text.clone())
                     .contained()
                     .with_style(style.container)
@@ -371,7 +379,7 @@ impl BufferSearchBar {
         enum NavButton {}
         MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme.search.option_button.style_for(state, false);
+            let style = theme.search.option_button.inactive_state().style_for(state);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -403,7 +411,7 @@ impl BufferSearchBar {
 
         enum CloseButton {}
         MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
-            let style = theme.dismiss_button.style_for(state, false);
+            let style = theme.dismiss_button.style_for(state);
             Svg::new("icons/x_mark_8.svg")
                 .with_color(style.color)
                 .constrained()
@@ -480,7 +488,7 @@ impl BufferSearchBar {
         self.select_match(Direction::Prev, cx);
     }
 
-    fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+    pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
         if let Some(index) = self.active_match_index {
             if let Some(searchable_item) = self.active_searchable_item.as_ref() {
                 if let Some(matches) = self

crates/search/src/project_search.rs πŸ”—

@@ -896,7 +896,7 @@ impl ProjectSearchBar {
         enum NavButton {}
         MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme.search.option_button.style_for(state, false);
+            let style = theme.search.option_button.inactive_state().style_for(state);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -927,7 +927,11 @@ impl ProjectSearchBar {
         let is_active = self.is_option_enabled(option, cx);
         MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme.search.option_button.style_for(state, is_active);
+            let style = theme
+                .search
+                .option_button
+                .in_state(is_active)
+                .style_for(state);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)

crates/theme/src/theme.rs πŸ”—

@@ -4,15 +4,16 @@ pub mod ui;
 
 use gpui::{
     color::Color,
-    elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
+    elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
     fonts::{HighlightStyle, TextStyle},
     platform, AppContext, AssetSource, Border, MouseState,
 };
+use schemars::JsonSchema;
 use serde::{de::DeserializeOwned, Deserialize};
 use serde_json::Value;
 use settings::SettingsStore;
 use std::{collections::HashMap, sync::Arc};
-use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle};
+use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle};
 
 pub use theme_registry::*;
 pub use theme_settings::*;
@@ -36,7 +37,7 @@ pub fn init(source: impl AssetSource, cx: &mut AppContext) {
     .detach();
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct Theme {
     #[serde(default)]
     pub meta: ThemeMeta,
@@ -65,9 +66,10 @@ pub struct Theme {
     pub feedback: FeedbackStyle,
     pub welcome: WelcomeStyle,
     pub color_scheme: ColorScheme,
+    pub titlebar: Titlebar,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct ThemeMeta {
     #[serde(skip_deserializing)]
     pub id: usize,
@@ -75,11 +77,10 @@ pub struct ThemeMeta {
     pub is_light: bool,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct Workspace {
     pub background: Color,
     pub blank_pane: BlankPaneStyle,
-    pub titlebar: Titlebar,
     pub tab_bar: TabBar,
     pub pane_divider: Border,
     pub leader_border_opacity: f32,
@@ -102,7 +103,7 @@ pub struct Workspace {
     pub drop_target_overlay_color: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct BlankPaneStyle {
     pub logo: SvgStyle,
     pub logo_shadow: SvgStyle,
@@ -112,7 +113,7 @@ pub struct BlankPaneStyle {
     pub keyboard_hint_width: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Titlebar {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -128,16 +129,34 @@ pub struct Titlebar {
     pub leader_avatar: AvatarStyle,
     pub follower_avatar: AvatarStyle,
     pub inactive_avatar_grayscale: bool,
-    pub sign_in_prompt: Interactive<ContainedText>,
+    pub sign_in_button: Toggleable<Interactive<ContainedText>>,
     pub outdated_warning: ContainedText,
-    pub share_button: Interactive<ContainedText>,
-    pub call_control: Interactive<IconButton>,
-    pub toggle_contacts_button: Interactive<IconButton>,
-    pub user_menu_button: Interactive<IconButton>,
+    pub share_button: Toggleable<Interactive<ContainedText>>,
+    pub muted: Color,
+    pub speaking: Color,
+    pub screen_share_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_microphone_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_speakers_button: Toggleable<Interactive<IconButton>>,
+    pub leave_call_button: Interactive<IconButton>,
     pub toggle_contacts_badge: ContainerStyle,
+    pub user_menu: UserMenu,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct UserMenu {
+    pub user_menu_button_online: UserMenuButton,
+    pub user_menu_button_offline: UserMenuButton,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct UserMenuButton {
+    pub user_menu: Toggleable<Interactive<Icon>>,
+    pub avatar: AvatarStyle,
+    pub icon: Icon,
 }
 
-#[derive(Copy, Clone, Deserialize, Default)]
+#[derive(Copy, Clone, Deserialize, Default, JsonSchema)]
 pub struct AvatarStyle {
     #[serde(flatten)]
     pub image: ImageStyle,
@@ -145,14 +164,14 @@ pub struct AvatarStyle {
     pub outer_corner_radius: f32,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct Copilot {
     pub out_link_icon: Interactive<IconStyle>,
     pub modal: ModalStyle,
     pub auth: CopilotAuth,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct CopilotAuth {
     pub content_width: f32,
     pub prompting: CopilotAuthPrompting,
@@ -162,14 +181,14 @@ pub struct CopilotAuth {
     pub header: IconStyle,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct CopilotAuthPrompting {
     pub subheading: ContainedText,
     pub hint: ContainedText,
     pub device_code: DeviceCode,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct DeviceCode {
     pub text: TextStyle,
     pub cta: ButtonStyle,
@@ -179,19 +198,19 @@ pub struct DeviceCode {
     pub right_container: Interactive<ContainerStyle>,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct CopilotAuthNotAuthorized {
     pub subheading: ContainedText,
     pub warning: ContainedText,
 }
 
-#[derive(Deserialize, Default, Clone)]
+#[derive(Deserialize, Default, Clone, JsonSchema)]
 pub struct CopilotAuthAuthorized {
     pub subheading: ContainedText,
     pub hint: ContainedText,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ContactsPopover {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -199,17 +218,17 @@ pub struct ContactsPopover {
     pub width: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ContactList {
     pub user_query_editor: FieldEditor,
     pub user_query_editor_height: f32,
     pub add_contact_button: IconButton,
-    pub header_row: Interactive<ContainedText>,
+    pub header_row: Toggleable<Interactive<ContainedText>>,
     pub leave_call: Interactive<ContainedText>,
-    pub contact_row: Interactive<ContainerStyle>,
+    pub contact_row: Toggleable<Interactive<ContainerStyle>>,
     pub row_height: f32,
-    pub project_row: Interactive<ProjectRow>,
-    pub tree_branch: Interactive<TreeBranch>,
+    pub project_row: Toggleable<Interactive<ProjectRow>>,
+    pub tree_branch: Toggleable<Interactive<TreeBranch>>,
     pub contact_avatar: ImageStyle,
     pub contact_status_free: ContainerStyle,
     pub contact_status_busy: ContainerStyle,
@@ -221,7 +240,7 @@ pub struct ContactList {
     pub calling_indicator: ContainedText,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ProjectRow {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -229,13 +248,13 @@ pub struct ProjectRow {
     pub name: ContainedText,
 }
 
-#[derive(Deserialize, Default, Clone, Copy)]
+#[derive(Deserialize, Default, Clone, Copy, JsonSchema)]
 pub struct TreeBranch {
     pub width: f32,
     pub color: Color,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ContactFinder {
     pub picker: Picker,
     pub row_height: f32,
@@ -245,17 +264,17 @@ pub struct ContactFinder {
     pub disabled_contact_button: IconButton,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct DropdownMenu {
     #[serde(flatten)]
     pub container: ContainerStyle,
     pub header: Interactive<DropdownMenuItem>,
     pub section_header: ContainedText,
-    pub item: Interactive<DropdownMenuItem>,
+    pub item: Toggleable<Interactive<DropdownMenuItem>>,
     pub row_height: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct DropdownMenuItem {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -266,11 +285,11 @@ pub struct DropdownMenuItem {
     pub secondary_text_spacing: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct TabBar {
     #[serde(flatten)]
     pub container: ContainerStyle,
-    pub pane_button: Interactive<IconButton>,
+    pub pane_button: Toggleable<Interactive<IconButton>>,
     pub pane_button_container: ContainerStyle,
     pub active_pane: TabStyles,
     pub inactive_pane: TabStyles,
@@ -294,13 +313,13 @@ impl TabBar {
     }
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct TabStyles {
     pub active_tab: Tab,
     pub inactive_tab: Tab,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct AvatarRibbon {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -308,7 +327,7 @@ pub struct AvatarRibbon {
     pub height: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct OfflineIcon {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -316,7 +335,7 @@ pub struct OfflineIcon {
     pub color: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Tab {
     pub height: f32,
     #[serde(flatten)]
@@ -333,7 +352,7 @@ pub struct Tab {
     pub icon_conflict: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Toolbar {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -342,14 +361,14 @@ pub struct Toolbar {
     pub nav_button: Interactive<IconButton>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Notifications {
     #[serde(flatten)]
     pub container: ContainerStyle,
     pub width: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Search {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -359,14 +378,14 @@ pub struct Search {
     pub include_exclude_editor: FindEditor,
     pub invalid_include_exclude_editor: ContainerStyle,
     pub include_exclude_inputs: ContainedText,
-    pub option_button: Interactive<ContainedText>,
+    pub option_button: Toggleable<Interactive<ContainedText>>,
     pub match_background: Color,
     pub match_index: ContainedText,
     pub results_status: TextStyle,
     pub dismiss_button: Interactive<IconButton>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct FindEditor {
     #[serde(flatten)]
     pub input: FieldEditor,
@@ -374,7 +393,7 @@ pub struct FindEditor {
     pub max_width: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct StatusBar {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -390,15 +409,15 @@ pub struct StatusBar {
     pub diagnostic_message: Interactive<ContainedText>,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct StatusBarPanelButtons {
     pub group_left: ContainerStyle,
     pub group_bottom: ContainerStyle,
     pub group_right: ContainerStyle,
-    pub button: Interactive<PanelButton>,
+    pub button: Toggleable<Interactive<PanelButton>>,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct StatusBarDiagnosticSummary {
     pub container_ok: ContainerStyle,
     pub container_warning: ContainerStyle,
@@ -413,7 +432,7 @@ pub struct StatusBarDiagnosticSummary {
     pub summary_spacing: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct StatusBarLspStatus {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -424,14 +443,14 @@ pub struct StatusBarLspStatus {
     pub message: TextStyle,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct Dock {
     pub left: ContainerStyle,
     pub bottom: ContainerStyle,
     pub right: ContainerStyle,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct PanelButton {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -440,20 +459,20 @@ pub struct PanelButton {
     pub label: ContainedText,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ProjectPanel {
     #[serde(flatten)]
     pub container: ContainerStyle,
-    pub entry: Interactive<ProjectPanelEntry>,
+    pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
     pub dragged_entry: ProjectPanelEntry,
-    pub ignored_entry: Interactive<ProjectPanelEntry>,
-    pub cut_entry: Interactive<ProjectPanelEntry>,
+    pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
+    pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
     pub filename_editor: FieldEditor,
     pub indent_width: f32,
     pub open_project_button: Interactive<ContainedText>,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct ProjectPanelEntry {
     pub height: f32,
     #[serde(flatten)]
@@ -465,28 +484,28 @@ pub struct ProjectPanelEntry {
     pub status: EntryStatus,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct EntryStatus {
     pub git: GitProjectStatus,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct GitProjectStatus {
     pub modified: Color,
     pub inserted: Color,
     pub conflict: Color,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct ContextMenu {
     #[serde(flatten)]
     pub container: ContainerStyle,
-    pub item: Interactive<ContextMenuItem>,
+    pub item: Toggleable<Interactive<ContextMenuItem>>,
     pub keystroke_margin: f32,
     pub separator: ContainerStyle,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct ContextMenuItem {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -496,13 +515,13 @@ pub struct ContextMenuItem {
     pub icon_spacing: f32,
 }
 
-#[derive(Debug, Deserialize, Default)]
+#[derive(Debug, Deserialize, Default, JsonSchema)]
 pub struct CommandPalette {
-    pub key: Interactive<ContainedLabel>,
+    pub key: Toggleable<ContainedLabel>,
     pub keystroke_spacing: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct InviteLink {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -511,7 +530,7 @@ pub struct InviteLink {
     pub icon: Icon,
 }
 
-#[derive(Deserialize, Clone, Copy, Default)]
+#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
 pub struct Icon {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -519,7 +538,7 @@ pub struct Icon {
     pub width: f32,
 }
 
-#[derive(Deserialize, Clone, Copy, Default)]
+#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
 pub struct IconButton {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -528,7 +547,7 @@ pub struct IconButton {
     pub button_width: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ChatMessage {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -537,7 +556,7 @@ pub struct ChatMessage {
     pub timestamp: ContainedText,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ChannelSelect {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -549,7 +568,7 @@ pub struct ChannelSelect {
     pub menu: ContainerStyle,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ChannelName {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -557,7 +576,7 @@ pub struct ChannelName {
     pub name: TextStyle,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Picker {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -565,10 +584,10 @@ pub struct Picker {
     pub input_editor: FieldEditor,
     pub empty_input_editor: FieldEditor,
     pub no_matches: ContainedLabel,
-    pub item: Interactive<ContainedLabel>,
+    pub item: Toggleable<Interactive<ContainedLabel>>,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct ContainedText {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -576,7 +595,7 @@ pub struct ContainedText {
     pub text: TextStyle,
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
 pub struct ContainedLabel {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -584,7 +603,7 @@ pub struct ContainedLabel {
     pub label: LabelStyle,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct ProjectDiagnostics {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -594,7 +613,7 @@ pub struct ProjectDiagnostics {
     pub tab_summary_spacing: f32,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ContactNotification {
     pub header_avatar: ImageStyle,
     pub header_message: ContainedText,
@@ -604,21 +623,21 @@ pub struct ContactNotification {
     pub dismiss_button: Interactive<IconButton>,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct UpdateNotification {
     pub message: ContainedText,
     pub action_message: Interactive<ContainedText>,
     pub dismiss_button: Interactive<IconButton>,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct MessageNotification {
     pub message: ContainedText,
     pub action_message: Interactive<ContainedText>,
     pub dismiss_button: Interactive<IconButton>,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct ProjectSharedNotification {
     pub window_height: f32,
     pub window_width: f32,
@@ -635,7 +654,7 @@ pub struct ProjectSharedNotification {
     pub dismiss_button: ContainedText,
 }
 
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, JsonSchema)]
 pub struct IncomingCallNotification {
     pub window_height: f32,
     pub window_width: f32,
@@ -652,7 +671,7 @@ pub struct IncomingCallNotification {
     pub decline_button: ContainedText,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Editor {
     pub text_color: Color,
     #[serde(default)]
@@ -693,7 +712,7 @@ pub struct Editor {
     pub whitespace: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Scrollbar {
     pub track: ContainerStyle,
     pub thumb: ContainerStyle,
@@ -702,14 +721,14 @@ pub struct Scrollbar {
     pub git: GitDiffColors,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct GitDiffColors {
     pub inserted: Color,
     pub modified: Color,
     pub deleted: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct DiagnosticPathHeader {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -718,7 +737,7 @@ pub struct DiagnosticPathHeader {
     pub text_scale_factor: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct DiagnosticHeader {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -729,7 +748,7 @@ pub struct DiagnosticHeader {
     pub icon_width_factor: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct DiagnosticStyle {
     pub message: LabelStyle,
     #[serde(default)]
@@ -737,7 +756,7 @@ pub struct DiagnosticStyle {
     pub text_scale_factor: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct AutocompleteStyle {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -747,13 +766,13 @@ pub struct AutocompleteStyle {
     pub match_highlight: HighlightStyle,
 }
 
-#[derive(Clone, Copy, Default, Deserialize)]
+#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
 pub struct SelectionStyle {
     pub cursor: Color,
     pub selection: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct FieldEditor {
     #[serde(flatten)]
     pub container: ContainerStyle,
@@ -763,21 +782,21 @@ pub struct FieldEditor {
     pub selection: SelectionStyle,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct InteractiveColor {
     pub color: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct CodeActions {
     #[serde(default)]
-    pub indicator: Interactive<InteractiveColor>,
+    pub indicator: Toggleable<Interactive<InteractiveColor>>,
     pub vertical_scale: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Folds {
-    pub indicator: Interactive<InteractiveColor>,
+    pub indicator: Toggleable<Interactive<InteractiveColor>>,
     pub ellipses: FoldEllipses,
     pub fold_background: Color,
     pub icon_margin_scale: f32,
@@ -785,14 +804,14 @@ pub struct Folds {
     pub foldable_icon: String,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct FoldEllipses {
     pub text_color: Color,
     pub background: Interactive<InteractiveColor>,
     pub corner_radius_factor: f32,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct DiffStyle {
     pub inserted: Color,
     pub modified: Color,
@@ -802,41 +821,49 @@ pub struct DiffStyle {
     pub corner_radius: f32,
 }
 
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(Debug, Default, Clone, Copy, JsonSchema)]
 pub struct Interactive<T> {
     pub default: T,
-    pub hover: Option<T>,
-    pub hover_and_active: Option<T>,
+    pub hovered: Option<T>,
     pub clicked: Option<T>,
-    pub click_and_active: Option<T>,
-    pub active: Option<T>,
     pub disabled: Option<T>,
 }
 
-impl<T> Interactive<T> {
-    pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
+#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
+pub struct Toggleable<T> {
+    active: T,
+    inactive: T,
+}
+
+impl<T> Toggleable<T> {
+    pub fn new(active: T, inactive: T) -> Self {
+        Self { active, inactive }
+    }
+    pub fn in_state(&self, active: bool) -> &T {
         if active {
-            if state.hovered() {
-                self.hover_and_active
-                    .as_ref()
-                    .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
-            } else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some()
-            {
-                self.click_and_active
-                    .as_ref()
-                    .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
-            } else {
-                self.active.as_ref().unwrap_or(&self.default)
-            }
-        } else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
+            &self.active
+        } else {
+            &self.inactive
+        }
+    }
+    pub fn active_state(&self) -> &T {
+        self.in_state(true)
+    }
+    pub fn inactive_state(&self) -> &T {
+        self.in_state(false)
+    }
+}
+
+impl<T> Interactive<T> {
+    pub fn style_for(&self, state: &mut MouseState) -> &T {
+        if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
             self.clicked.as_ref().unwrap()
         } else if state.hovered() {
-            self.hover.as_ref().unwrap_or(&self.default)
+            self.hovered.as_ref().unwrap_or(&self.default)
         } else {
             &self.default
         }
     }
-
     pub fn disabled_style(&self) -> &T {
         self.disabled.as_ref().unwrap_or(&self.default)
     }
@@ -849,13 +876,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
     {
         #[derive(Deserialize)]
         struct Helper {
-            #[serde(flatten)]
             default: Value,
-            hover: Option<Value>,
-            hover_and_active: Option<Value>,
+            hovered: Option<Value>,
             clicked: Option<Value>,
-            click_and_active: Option<Value>,
-            active: Option<Value>,
             disabled: Option<Value>,
         }
 
@@ -880,21 +903,15 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
             }
         };
 
-        let hover = deserialize_state(json.hover)?;
-        let hover_and_active = deserialize_state(json.hover_and_active)?;
+        let hovered = deserialize_state(json.hovered)?;
         let clicked = deserialize_state(json.clicked)?;
-        let click_and_active = deserialize_state(json.click_and_active)?;
-        let active = deserialize_state(json.active)?;
         let disabled = deserialize_state(json.disabled)?;
         let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
 
         Ok(Interactive {
             default,
-            hover,
-            hover_and_active,
+            hovered,
             clicked,
-            click_and_active,
-            active,
             disabled,
         })
     }
@@ -911,7 +928,7 @@ impl Editor {
     }
 }
 
-#[derive(Default)]
+#[derive(Default, JsonSchema)]
 pub struct SyntaxTheme {
     pub highlights: Vec<(String, HighlightStyle)>,
 }
@@ -945,7 +962,7 @@ impl<'de> Deserialize<'de> for SyntaxTheme {
     }
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct HoverPopover {
     pub container: ContainerStyle,
     pub info_container: ContainerStyle,
@@ -957,7 +974,7 @@ pub struct HoverPopover {
     pub highlight: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct TerminalStyle {
     pub black: Color,
     pub red: Color,
@@ -991,24 +1008,39 @@ pub struct TerminalStyle {
     pub dim_foreground: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct AssistantStyle {
     pub container: ContainerStyle,
-    pub header: ContainerStyle,
+    pub hamburger_button: Interactive<IconStyle>,
+    pub split_button: Interactive<IconStyle>,
+    pub assist_button: Interactive<IconStyle>,
+    pub quote_button: Interactive<IconStyle>,
+    pub zoom_in_button: Interactive<IconStyle>,
+    pub zoom_out_button: Interactive<IconStyle>,
+    pub plus_button: Interactive<IconStyle>,
+    pub title: ContainedText,
+    pub message_header: ContainerStyle,
     pub sent_at: ContainedText,
     pub user_sender: Interactive<ContainedText>,
     pub assistant_sender: Interactive<ContainedText>,
     pub system_sender: Interactive<ContainedText>,
-    pub model_info_container: ContainerStyle,
     pub model: Interactive<ContainedText>,
     pub remaining_tokens: ContainedText,
     pub no_remaining_tokens: ContainedText,
     pub error_icon: Icon,
     pub api_key_editor: FieldEditor,
     pub api_key_prompt: ContainedText,
+    pub saved_conversation: SavedConversation,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct SavedConversation {
+    pub container: Interactive<ContainerStyle>,
+    pub saved_at: ContainedText,
+    pub title: ContainedText,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct FeedbackStyle {
     pub submit_button: Interactive<ContainedText>,
     pub button_margin: f32,
@@ -1017,7 +1049,7 @@ pub struct FeedbackStyle {
     pub link_text_hover: ContainedText,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct WelcomeStyle {
     pub page_width: f32,
     pub logo: SvgStyle,
@@ -1031,7 +1063,7 @@ pub struct WelcomeStyle {
     pub checkbox_group: ContainerStyle,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct ColorScheme {
     pub name: String,
     pub is_light: bool,
@@ -1046,13 +1078,13 @@ pub struct ColorScheme {
     pub players: Vec<Player>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Player {
     pub cursor: Color,
     pub selection: Color,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct RampSet {
     pub neutral: Vec<Color>,
     pub red: Vec<Color>,
@@ -1065,7 +1097,7 @@ pub struct RampSet {
     pub magenta: Vec<Color>,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Layer {
     pub base: StyleSet,
     pub variant: StyleSet,
@@ -1076,7 +1108,7 @@ pub struct Layer {
     pub negative: StyleSet,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct StyleSet {
     pub default: Style,
     pub active: Style,
@@ -1086,7 +1118,7 @@ pub struct StyleSet {
     pub inverted: Style,
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct Style {
     pub background: Color,
     pub border: Color,

crates/theme/src/theme_settings.rs πŸ”—

@@ -14,12 +14,13 @@ use util::ResultExt as _;
 
 const MIN_FONT_SIZE: f32 = 6.0;
 
-#[derive(Clone)]
+#[derive(Clone, JsonSchema)]
 pub struct ThemeSettings {
     pub buffer_font_family_name: String,
     pub buffer_font_features: fonts::Features,
     pub buffer_font_family: FamilyId,
     pub(crate) buffer_font_size: f32,
+    #[serde(skip)]
     pub theme: Arc<Theme>,
 }
 

crates/theme/src/ui.rs πŸ”—

@@ -1,23 +1,23 @@
 use std::borrow::Cow;
 
 use gpui::{
-    color::Color,
     elements::{
-        ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
-        MouseEventHandler, ParentElement, Stack, Svg,
+        ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label,
+        MouseEventHandler, ParentElement, Stack, Svg, SvgStyle,
     },
     fonts::TextStyle,
-    geometry::vector::{vec2f, Vector2F},
+    geometry::vector::Vector2F,
     platform,
     platform::MouseButton,
     scene::MouseClick,
     Action, Element, EventContext, MouseState, View, ViewContext,
 };
+use schemars::JsonSchema;
 use serde::Deserialize;
 
 use crate::{ContainedText, Interactive};
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct CheckboxStyle {
     pub icon: SvgStyle,
     pub label: ContainedText,
@@ -93,25 +93,6 @@ where
     .with_cursor_style(platform::CursorStyle::PointingHand)
 }
 
-#[derive(Clone, Deserialize, Default)]
-pub struct SvgStyle {
-    pub color: Color,
-    pub asset: String,
-    pub dimensions: Dimensions,
-}
-
-#[derive(Clone, Deserialize, Default)]
-pub struct Dimensions {
-    pub width: f32,
-    pub height: f32,
-}
-
-impl Dimensions {
-    pub fn to_vec(&self) -> Vector2F {
-        vec2f(self.width, self.height)
-    }
-}
-
 pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
     Svg::new(style.asset.clone())
         .with_color(style.color)
@@ -120,10 +101,10 @@ pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
         .with_height(style.dimensions.height)
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct IconStyle {
-    icon: SvgStyle,
-    container: ContainerStyle,
+    pub icon: SvgStyle,
+    pub container: ContainerStyle,
 }
 
 pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
@@ -170,7 +151,7 @@ where
     F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
 {
     MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
-        let style = style.style_for(state, false);
+        let style = style.style_for(state);
         Label::new(label, style.text.to_owned())
             .aligned()
             .contained()
@@ -182,7 +163,7 @@ where
     .with_cursor_style(platform::CursorStyle::PointingHand)
 }
 
-#[derive(Clone, Deserialize, Default)]
+#[derive(Clone, Deserialize, Default, JsonSchema)]
 pub struct ModalStyle {
     close_icon: Interactive<IconStyle>,
     container: ContainerStyle,
@@ -220,13 +201,13 @@ where
                     title,
                     style
                         .title_text
-                        .style_for(&mut MouseState::default(), false)
+                        .style_for(&mut MouseState::default())
                         .clone(),
                 ))
                 .with_child(
                     // FIXME: Get a better tag type
                     MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
-                        let style = style.close_icon.style_for(state, false);
+                        let style = style.close_icon.style_for(state);
                         icon(style)
                     })
                     .on_click(platform::MouseButton::Left, move |_, _, cx| {

crates/theme_selector/src/theme_selector.rs πŸ”—

@@ -208,7 +208,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
         let theme = theme::current(cx);
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
 
         let theme_match = &self.matches[ix];
         Label::new(theme_match.string.clone(), style.label.clone())

crates/theme_testbench/Cargo.toml πŸ”—

@@ -1,19 +0,0 @@
-[package]
-name = "theme_testbench"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/theme_testbench.rs"
-doctest = false
-
-
-[dependencies]
-gpui = { path = "../gpui" }
-theme = { path = "../theme" }
-settings = { path = "../settings" }
-workspace = { path = "../workspace" }
-project = { path = "../project" }
-
-smallvec.workspace = true

crates/theme_testbench/src/theme_testbench.rs πŸ”—

@@ -1,300 +0,0 @@
-use gpui::{
-    actions,
-    color::Color,
-    elements::{
-        AnyElement, Canvas, Container, ContainerStyle, Flex, Label, Margin, MouseEventHandler,
-        Padding, ParentElement,
-    },
-    fonts::TextStyle,
-    AppContext, Border, Element, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
-};
-use project::Project;
-use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings};
-use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
-
-actions!(theme, [DeployThemeTestbench]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(ThemeTestbench::deploy);
-
-    register_deserializable_item::<ThemeTestbench>(cx)
-}
-
-pub struct ThemeTestbench {}
-
-impl ThemeTestbench {
-    pub fn deploy(
-        workspace: &mut Workspace,
-        _: &DeployThemeTestbench,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let view = cx.add_view(|_| ThemeTestbench {});
-        workspace.add_item(Box::new(view), cx);
-    }
-
-    fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
-        fn display_ramp(ramp: &Vec<Color>) -> AnyElement<ThemeTestbench> {
-            Flex::row()
-                .with_children(ramp.iter().cloned().map(|color| {
-                    Canvas::new(move |scene, bounds, _, _, _| {
-                        scene.push_quad(Quad {
-                            bounds,
-                            background: Some(color),
-                            ..Default::default()
-                        });
-                    })
-                    .flex(1.0, false)
-                }))
-                .flex(1.0, false)
-                .into_any()
-        }
-
-        Flex::column()
-            .with_child(display_ramp(&color_scheme.ramps.neutral))
-            .with_child(display_ramp(&color_scheme.ramps.red))
-            .with_child(display_ramp(&color_scheme.ramps.orange))
-            .with_child(display_ramp(&color_scheme.ramps.yellow))
-            .with_child(display_ramp(&color_scheme.ramps.green))
-            .with_child(display_ramp(&color_scheme.ramps.cyan))
-            .with_child(display_ramp(&color_scheme.ramps.blue))
-            .with_child(display_ramp(&color_scheme.ramps.violet))
-            .with_child(display_ramp(&color_scheme.ramps.magenta))
-    }
-
-    fn render_layer(
-        layer_index: usize,
-        layer: &Layer,
-        cx: &mut ViewContext<Self>,
-    ) -> Container<Self> {
-        Flex::column()
-            .with_child(
-                Self::render_button_set(0, layer_index, "base", &layer.base, cx).flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
-                    .flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(2, layer_index, "on", &layer.on, cx).flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
-                    .flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
-                    .flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
-                    .flex(1., false),
-            )
-            .with_child(
-                Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
-                    .flex(1., false),
-            )
-            .contained()
-            .with_style(ContainerStyle {
-                margin: Margin {
-                    top: 10.,
-                    bottom: 10.,
-                    left: 10.,
-                    right: 10.,
-                },
-                background_color: Some(layer.base.default.background),
-                ..Default::default()
-            })
-    }
-
-    fn render_button_set(
-        set_index: usize,
-        layer_index: usize,
-        set_name: &'static str,
-        style_set: &StyleSet,
-        cx: &mut ViewContext<Self>,
-    ) -> Flex<Self> {
-        Flex::row()
-            .with_child(Self::render_button(
-                set_index * 6,
-                layer_index,
-                set_name,
-                &style_set,
-                None,
-                cx,
-            ))
-            .with_child(Self::render_button(
-                set_index * 6 + 1,
-                layer_index,
-                "hovered",
-                &style_set,
-                Some(|style_set| &style_set.hovered),
-                cx,
-            ))
-            .with_child(Self::render_button(
-                set_index * 6 + 2,
-                layer_index,
-                "pressed",
-                &style_set,
-                Some(|style_set| &style_set.pressed),
-                cx,
-            ))
-            .with_child(Self::render_button(
-                set_index * 6 + 3,
-                layer_index,
-                "active",
-                &style_set,
-                Some(|style_set| &style_set.active),
-                cx,
-            ))
-            .with_child(Self::render_button(
-                set_index * 6 + 4,
-                layer_index,
-                "disabled",
-                &style_set,
-                Some(|style_set| &style_set.disabled),
-                cx,
-            ))
-            .with_child(Self::render_button(
-                set_index * 6 + 5,
-                layer_index,
-                "inverted",
-                &style_set,
-                Some(|style_set| &style_set.inverted),
-                cx,
-            ))
-    }
-
-    fn render_button(
-        button_index: usize,
-        layer_index: usize,
-        text: &'static str,
-        style_set: &StyleSet,
-        style_override: Option<fn(&StyleSet) -> &Style>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum TestBenchButton {}
-        MouseEventHandler::<TestBenchButton, _>::new(layer_index + button_index, cx, |state, cx| {
-            let style = if let Some(style_override) = style_override {
-                style_override(&style_set)
-            } else if state.clicked().is_some() {
-                &style_set.pressed
-            } else if state.hovered() {
-                &style_set.hovered
-            } else {
-                &style_set.default
-            };
-
-            Self::render_label(text.to_string(), style, cx)
-                .contained()
-                .with_style(ContainerStyle {
-                    margin: Margin {
-                        top: 4.,
-                        bottom: 4.,
-                        left: 4.,
-                        right: 4.,
-                    },
-                    padding: Padding {
-                        top: 4.,
-                        bottom: 4.,
-                        left: 4.,
-                        right: 4.,
-                    },
-                    background_color: Some(style.background),
-                    border: Border {
-                        width: 1.,
-                        color: style.border,
-                        overlay: false,
-                        top: true,
-                        bottom: true,
-                        left: true,
-                        right: true,
-                    },
-                    corner_radius: 2.,
-                    ..Default::default()
-                })
-        })
-        .flex(1., true)
-        .into_any()
-    }
-
-    fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
-        let settings = settings::get::<ThemeSettings>(cx);
-        let font_cache = cx.font_cache();
-        let family_id = settings.buffer_font_family;
-        let font_size = settings.buffer_font_size(cx);
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-
-        let text_style = TextStyle {
-            color: style.foreground,
-            font_family_id: family_id,
-            font_family_name: font_cache.family_name(family_id).unwrap(),
-            font_id,
-            font_size,
-            font_properties: Default::default(),
-            underline: Default::default(),
-        };
-
-        Label::new(text, text_style)
-    }
-}
-
-impl Entity for ThemeTestbench {
-    type Event = ();
-}
-
-impl View for ThemeTestbench {
-    fn ui_name() -> &'static str {
-        "ThemeTestbench"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
-        let color_scheme = &theme::current(cx).clone().color_scheme;
-
-        Flex::row()
-            .with_child(
-                Self::render_ramps(color_scheme)
-                    .contained()
-                    .with_margin_right(10.)
-                    .flex(0.1, false),
-            )
-            .with_child(
-                Flex::column()
-                    .with_child(Self::render_layer(100, &color_scheme.lowest, cx).flex(1., true))
-                    .with_child(Self::render_layer(200, &color_scheme.middle, cx).flex(1., true))
-                    .with_child(Self::render_layer(300, &color_scheme.highest, cx).flex(1., true))
-                    .flex(1., false),
-            )
-            .into_any()
-    }
-}
-
-impl Item for ThemeTestbench {
-    fn tab_content<T: View>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> AnyElement<T> {
-        Label::new("Theme Testbench", style.label.clone())
-            .aligned()
-            .contained()
-            .into_any()
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("ThemeTestBench")
-    }
-
-    fn deserialize(
-        _project: ModelHandle<Project>,
-        _workspace: WeakViewHandle<Workspace>,
-        _workspace_id: workspace::WorkspaceId,
-        _item_id: workspace::ItemId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
-        Task::ready(Ok(cx.add_view(|_| Self {})))
-    }
-}

crates/util/src/paths.rs πŸ”—

@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
 lazy_static::lazy_static! {
     pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory");
     pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed");
+    pub static ref CONVERSATIONS_DIR: PathBuf = HOME.join(".config/zed/conversations");
     pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed");
     pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed");
     pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages");

crates/vim/src/motion.rs πŸ”—

@@ -209,8 +209,9 @@ impl Motion {
         map: &DisplaySnapshot,
         point: DisplayPoint,
         goal: SelectionGoal,
-        times: usize,
+        maybe_times: Option<usize>,
     ) -> Option<(DisplayPoint, SelectionGoal)> {
+        let times = maybe_times.unwrap_or(1);
         use Motion::*;
         let infallible = self.infallible();
         let (new_point, goal) = match self {
@@ -236,7 +237,10 @@ impl Motion {
             EndOfLine => (end_of_line(map, point), SelectionGoal::None),
             CurrentLine => (end_of_line(map, point), SelectionGoal::None),
             StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
-            EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None),
+            EndOfDocument => (
+                end_of_document(map, point, maybe_times),
+                SelectionGoal::None,
+            ),
             Matching => (matching(map, point), SelectionGoal::None),
             FindForward { before, text } => (
                 find_forward(map, point, *before, text.clone(), times),
@@ -257,7 +261,7 @@ impl Motion {
         &self,
         map: &DisplaySnapshot,
         selection: &mut Selection<DisplayPoint>,
-        times: usize,
+        times: Option<usize>,
         expand_to_surrounding_newline: bool,
     ) -> bool {
         if let Some((new_head, goal)) =
@@ -473,14 +477,19 @@ fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) ->
     map.clip_point(new_point, Bias::Left)
 }
 
-fn end_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
-    let mut new_point = if line == 1 {
-        map.max_point()
+fn end_of_document(
+    map: &DisplaySnapshot,
+    point: DisplayPoint,
+    line: Option<usize>,
+) -> DisplayPoint {
+    let new_row = if let Some(line) = line {
+        (line - 1) as u32
     } else {
-        Point::new((line - 1) as u32, 0).to_display_point(map)
+        map.max_buffer_row()
     };
-    *new_point.column_mut() = point.column();
-    map.clip_point(new_point, Bias::Left)
+
+    let new_point = Point::new(new_row, point.column());
+    map.clip_point(new_point.to_display_point(map), Bias::Left)
 }
 
 fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {

crates/vim/src/normal.rs πŸ”—

@@ -1,8 +1,11 @@
+mod case;
 mod change;
 mod delete;
+mod scroll;
+mod substitute;
 mod yank;
 
-use std::{borrow::Cow, cmp::Ordering, sync::Arc};
+use std::{borrow::Cow, sync::Arc};
 
 use crate::{
     motion::Motion,
@@ -12,25 +15,22 @@ use crate::{
 };
 use collections::{HashMap, HashSet};
 use editor::{
-    display_map::ToDisplayPoint,
-    scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
-    Anchor, Bias, ClipboardSelection, DisplayPoint, Editor,
+    display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, Bias, ClipboardSelection,
+    DisplayPoint,
 };
-use gpui::{actions, impl_actions, AppContext, ViewContext, WindowContext};
+use gpui::{actions, AppContext, ViewContext, WindowContext};
 use language::{AutoindentMode, Point, SelectionGoal};
 use log::error;
-use serde::Deserialize;
 use workspace::Workspace;
 
 use self::{
+    case::change_case,
     change::{change_motion, change_object},
     delete::{delete_motion, delete_object},
+    substitute::substitute,
     yank::{yank_motion, yank_object},
 };
 
-#[derive(Clone, PartialEq, Deserialize)]
-struct Scroll(ScrollAmount);
-
 actions!(
     vim,
     [
@@ -45,17 +45,24 @@ actions!(
         DeleteToEndOfLine,
         Paste,
         Yank,
+        Substitute,
+        ChangeCase,
     ]
 );
 
-impl_actions!(vim, [Scroll]);
-
 pub fn init(cx: &mut AppContext) {
     cx.add_action(insert_after);
     cx.add_action(insert_first_non_whitespace);
     cx.add_action(insert_end_of_line);
     cx.add_action(insert_line_above);
     cx.add_action(insert_line_below);
+    cx.add_action(change_case);
+    cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
+        Vim::update(cx, |vim, cx| {
+            let times = vim.pop_number_operator(cx);
+            substitute(vim, times, cx);
+        })
+    });
     cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
         Vim::update(cx, |vim, cx| {
             let times = vim.pop_number_operator(cx);
@@ -81,19 +88,14 @@ pub fn init(cx: &mut AppContext) {
         })
     });
     cx.add_action(paste);
-    cx.add_action(|_: &mut Workspace, Scroll(amount): &Scroll, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.update_active_editor(cx, |editor, cx| {
-                scroll(editor, amount, cx);
-            })
-        })
-    });
+
+    scroll::init(cx);
 }
 
 pub fn normal_motion(
     motion: Motion,
     operator: Option<Operator>,
-    times: usize,
+    times: Option<usize>,
     cx: &mut WindowContext,
 ) {
     Vim::update(cx, |vim, cx| {
@@ -129,7 +131,7 @@ pub fn normal_object(object: Object, cx: &mut WindowContext) {
     })
 }
 
-fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
+fn move_cursor(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
             s.move_cursors_with(|map, cursor, goal| {
@@ -147,7 +149,7 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspa
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                 s.maybe_move_cursors_with(|map, cursor, goal| {
-                    Motion::Right.move_point(map, cursor, goal, 1)
+                    Motion::Right.move_point(map, cursor, goal, None)
                 });
             });
         });
@@ -164,7 +166,7 @@ fn insert_first_non_whitespace(
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                 s.maybe_move_cursors_with(|map, cursor, goal| {
-                    Motion::FirstNonWhitespace.move_point(map, cursor, goal, 1)
+                    Motion::FirstNonWhitespace.move_point(map, cursor, goal, None)
                 });
             });
         });
@@ -177,7 +179,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                 s.maybe_move_cursors_with(|map, cursor, goal| {
-                    Motion::EndOfLine.move_point(map, cursor, goal, 1)
+                    Motion::EndOfLine.move_point(map, cursor, goal, None)
                 });
             });
         });
@@ -237,7 +239,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
                 });
                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                     s.maybe_move_cursors_with(|map, cursor, goal| {
-                        Motion::EndOfLine.move_point(map, cursor, goal, 1)
+                        Motion::EndOfLine.move_point(map, cursor, goal, None)
                     });
                 });
                 editor.edit_with_autoindent(edits, cx);
@@ -384,46 +386,6 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
     });
 }
 
-fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
-    let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
-    editor.scroll_screen(amount, cx);
-    if should_move_cursor {
-        let selection_ordering = editor.newest_selection_on_screen(cx);
-        if selection_ordering.is_eq() {
-            return;
-        }
-
-        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
-            visible_rows as u32
-        } else {
-            return;
-        };
-
-        let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
-        let top_anchor = editor.scroll_manager.anchor().anchor;
-
-        editor.change_selections(None, cx, |s| {
-            s.replace_cursors_with(|snapshot| {
-                let mut new_point = top_anchor.to_display_point(&snapshot);
-
-                match selection_ordering {
-                    Ordering::Less => {
-                        *new_point.row_mut() += scroll_margin_rows;
-                        new_point = snapshot.clip_point(new_point, Bias::Right);
-                    }
-                    Ordering::Greater => {
-                        *new_point.row_mut() += visible_rows - scroll_margin_rows as u32;
-                        new_point = snapshot.clip_point(new_point, Bias::Left);
-                    }
-                    Ordering::Equal => unreachable!(),
-                }
-
-                vec![new_point]
-            })
-        });
-    }
-}
-
 pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {

crates/vim/src/normal/case.rs πŸ”—

@@ -0,0 +1,64 @@
+use gpui::ViewContext;
+use language::Point;
+use workspace::Workspace;
+
+use crate::{motion::Motion, normal::ChangeCase, Vim};
+
+pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        let count = vim.pop_number_operator(cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.set_clip_at_line_ends(false, cx);
+            editor.transact(cx, |editor, cx| {
+                editor.change_selections(None, cx, |s| {
+                    s.move_with(|map, selection| {
+                        if selection.start == selection.end {
+                            Motion::Right.expand_selection(map, selection, count, true);
+                        }
+                    })
+                });
+                let selections = editor.selections.all::<Point>(cx);
+                for selection in selections.into_iter().rev() {
+                    let snapshot = editor.buffer().read(cx).snapshot(cx);
+                    editor.buffer().update(cx, |buffer, cx| {
+                        let range = selection.start..selection.end;
+                        let text = snapshot
+                            .text_for_range(selection.start..selection.end)
+                            .flat_map(|s| s.chars())
+                            .flat_map(|c| {
+                                if c.is_lowercase() {
+                                    c.to_uppercase().collect::<Vec<char>>()
+                                } else {
+                                    c.to_lowercase().collect::<Vec<char>>()
+                                }
+                            })
+                            .collect::<String>();
+
+                        buffer.edit([(range, text)], None, cx)
+                    })
+                }
+            });
+            editor.set_clip_at_line_ends(true, cx);
+        });
+    })
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{state::Mode, test::VimTestContext};
+    use indoc::indoc;
+
+    #[gpui::test]
+    async fn test_change_case(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+        cx.set_state(indoc! {"Λ‡abC\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["~"]);
+        cx.assert_editor_state("AˇbC\n");
+        cx.simulate_keystrokes(["2", "~"]);
+        cx.assert_editor_state("ABcˇ\n");
+
+        cx.set_state(indoc! {"aπŸ˜€CΒ«dΓ‰1*fΛ‡Β»\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["~"]);
+        cx.assert_editor_state("aπŸ˜€CDΓ©1*FΛ‡\n");
+    }
+}

crates/vim/src/normal/change.rs πŸ”—

@@ -6,7 +6,7 @@ use editor::{
 use gpui::WindowContext;
 use language::Selection;
 
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
+pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     // Some motions ignore failure when switching to normal mode
     let mut motion_succeeded = matches!(
         motion,
@@ -78,10 +78,10 @@ pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo
 fn expand_changed_word_selection(
     map: &DisplaySnapshot,
     selection: &mut Selection<DisplayPoint>,
-    times: usize,
+    times: Option<usize>,
     ignore_punctuation: bool,
 ) -> bool {
-    if times == 1 {
+    if times.is_none() || times.unwrap() == 1 {
         let in_word = map
             .chars_at(selection.head())
             .next()
@@ -97,7 +97,8 @@ fn expand_changed_word_selection(
             });
             true
         } else {
-            Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, 1, false)
+            Motion::NextWordStart { ignore_punctuation }
+                .expand_selection(map, selection, None, false)
         }
     } else {
         Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, times, false)

crates/vim/src/normal/delete.rs πŸ”—

@@ -3,7 +3,7 @@ use collections::{HashMap, HashSet};
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
 use gpui::WindowContext;
 
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
+pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);

crates/vim/src/normal/scroll.rs πŸ”—

@@ -0,0 +1,120 @@
+use std::cmp::Ordering;
+
+use crate::Vim;
+use editor::{display_map::ToDisplayPoint, scroll::scroll_amount::ScrollAmount, Editor};
+use gpui::{actions, AppContext, ViewContext};
+use language::Bias;
+use workspace::Workspace;
+
+actions!(
+    vim,
+    [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown,]
+);
+
+pub fn init(cx: &mut AppContext) {
+    cx.add_action(|_: &mut Workspace, _: &LineDown, cx| {
+        scroll(cx, |c| ScrollAmount::Line(c.unwrap_or(1.)))
+    });
+    cx.add_action(|_: &mut Workspace, _: &LineUp, cx| {
+        scroll(cx, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
+    });
+    cx.add_action(|_: &mut Workspace, _: &PageDown, cx| {
+        scroll(cx, |c| ScrollAmount::Page(c.unwrap_or(1.)))
+    });
+    cx.add_action(|_: &mut Workspace, _: &PageUp, cx| {
+        scroll(cx, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
+    });
+    cx.add_action(|_: &mut Workspace, _: &ScrollDown, cx| {
+        scroll(cx, |c| {
+            if let Some(c) = c {
+                ScrollAmount::Line(c)
+            } else {
+                ScrollAmount::Page(0.5)
+            }
+        })
+    });
+    cx.add_action(|_: &mut Workspace, _: &ScrollUp, cx| {
+        scroll(cx, |c| {
+            if let Some(c) = c {
+                ScrollAmount::Line(-c)
+            } else {
+                ScrollAmount::Page(-0.5)
+            }
+        })
+    });
+}
+
+fn scroll(cx: &mut ViewContext<Workspace>, by: fn(c: Option<f32>) -> ScrollAmount) {
+    Vim::update(cx, |vim, cx| {
+        let amount = by(vim.pop_number_operator(cx).map(|c| c as f32));
+        vim.update_active_editor(cx, |editor, cx| scroll_editor(editor, &amount, cx));
+    })
+}
+
+fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
+    let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
+    editor.scroll_screen(amount, cx);
+    if should_move_cursor {
+        let selection_ordering = editor.newest_selection_on_screen(cx);
+        if selection_ordering.is_eq() {
+            return;
+        }
+
+        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
+            visible_rows as u32
+        } else {
+            return;
+        };
+
+        let top_anchor = editor.scroll_manager.anchor().anchor;
+
+        editor.change_selections(None, cx, |s| {
+            s.replace_cursors_with(|snapshot| {
+                let mut new_point = top_anchor.to_display_point(&snapshot);
+
+                match selection_ordering {
+                    Ordering::Less => {
+                        new_point = snapshot.clip_point(new_point, Bias::Right);
+                    }
+                    Ordering::Greater => {
+                        *new_point.row_mut() += visible_rows - 1;
+                        new_point = snapshot.clip_point(new_point, Bias::Left);
+                    }
+                    Ordering::Equal => unreachable!(),
+                }
+
+                vec![new_point]
+            })
+        });
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{state::Mode, test::VimTestContext};
+    use gpui::geometry::vector::vec2f;
+    use indoc::indoc;
+
+    #[gpui::test]
+    async fn test_scroll(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+
+        cx.set_state(indoc! {"Λ‡a\nb\nc\nd\ne\n"}, Mode::Normal);
+
+        cx.update_editor(|editor, cx| {
+            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+        });
+        cx.simulate_keystrokes(["ctrl-e"]);
+        cx.update_editor(|editor, cx| {
+            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
+        });
+        cx.simulate_keystrokes(["2", "ctrl-e"]);
+        cx.update_editor(|editor, cx| {
+            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
+        });
+        cx.simulate_keystrokes(["ctrl-y"]);
+        cx.update_editor(|editor, cx| {
+            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
+        });
+    }
+}

crates/vim/src/normal/substitute.rs πŸ”—

@@ -0,0 +1,73 @@
+use gpui::WindowContext;
+use language::Point;
+
+use crate::{motion::Motion, Mode, Vim};
+
+pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
+    vim.update_active_editor(cx, |editor, cx| {
+        editor.set_clip_at_line_ends(false, cx);
+        editor.transact(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    if selection.start == selection.end {
+                        Motion::Right.expand_selection(map, selection, count, true);
+                    }
+                })
+            });
+            let selections = editor.selections.all::<Point>(cx);
+            for selection in selections.into_iter().rev() {
+                editor.buffer().update(cx, |buffer, cx| {
+                    buffer.edit([(selection.start..selection.end, "")], None, cx)
+                })
+            }
+        });
+        editor.set_clip_at_line_ends(true, cx);
+    });
+    vim.switch_mode(Mode::Insert, true, cx)
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{state::Mode, test::VimTestContext};
+    use indoc::indoc;
+
+    #[gpui::test]
+    async fn test_substitute(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+
+        // supports a single cursor
+        cx.set_state(indoc! {"Λ‡abc\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["s", "x"]);
+        cx.assert_editor_state("xˇbc\n");
+
+        // supports a selection
+        cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false });
+        cx.assert_editor_state("a«bcˇ»\n");
+        cx.simulate_keystrokes(["s", "x"]);
+        cx.assert_editor_state("axˇ\n");
+
+        // supports counts
+        cx.set_state(indoc! {"Λ‡abc\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["2", "s", "x"]);
+        cx.assert_editor_state("xˇc\n");
+
+        // supports multiple cursors
+        cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["2", "s", "x"]);
+        cx.assert_editor_state("axˇdexˇg\n");
+
+        // does not read beyond end of line
+        cx.set_state(indoc! {"Λ‡abc\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["5", "s", "x"]);
+        cx.assert_editor_state("xˇ\n");
+
+        // it handles multibyte characters
+        cx.set_state(indoc! {"Λ‡cΓ fΓ©\n"}, Mode::Normal);
+        cx.simulate_keystrokes(["4", "s"]);
+        cx.assert_editor_state("Λ‡\n");
+
+        // should transactionally undo selection changes
+        cx.simulate_keystrokes(["escape", "u"]);
+        cx.assert_editor_state("Λ‡cΓ fΓ©\n");
+    }
+}

crates/vim/src/normal/yank.rs πŸ”—

@@ -2,7 +2,7 @@ use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim}
 use collections::HashMap;
 use gpui::WindowContext;
 
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
+pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);

crates/vim/src/test.rs πŸ”—

@@ -98,3 +98,44 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
         assert_eq!(bar.query_editor.read(cx).text(cx), "jumps");
     })
 }
+
+#[gpui::test]
+async fn test_count_down(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
+    cx.simulate_keystrokes(["2", "down"]);
+    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
+    cx.simulate_keystrokes(["9", "down"]);
+    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
+}
+
+#[gpui::test]
+async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    // goes to end by default
+    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
+    cx.simulate_keystrokes(["shift-g"]);
+    cx.assert_editor_state("aa\nbb\ncˇc");
+
+    // can go to line 1 (https://github.com/zed-industries/community/issues/710)
+    cx.simulate_keystrokes(["1", "shift-g"]);
+    cx.assert_editor_state("aˇa\nbb\ncc");
+}
+
+#[gpui::test]
+async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    // works in normal mode
+    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
+    cx.simulate_keystrokes([">", ">"]);
+    cx.assert_editor_state("aa\n    bˇb\ncc");
+    cx.simulate_keystrokes(["<", "<"]);
+    cx.assert_editor_state("aa\nbˇb\ncc");
+
+    // works in visuial mode
+    cx.simulate_keystrokes(["shift-v", "down", ">", ">"]);
+    cx.assert_editor_state("aa\n    b«b\n    cˇ»c");
+}

crates/vim/src/vim.rs πŸ”—

@@ -238,13 +238,12 @@ impl Vim {
         popped_operator
     }
 
-    fn pop_number_operator(&mut self, cx: &mut WindowContext) -> usize {
-        let mut times = 1;
+    fn pop_number_operator(&mut self, cx: &mut WindowContext) -> Option<usize> {
         if let Some(Operator::Number(number)) = self.active_operator() {
-            times = number;
             self.pop_operator(cx);
+            return Some(number);
         }
-        times
+        None
     }
 
     fn clear_operator(&mut self, cx: &mut WindowContext) {

crates/vim/src/visual.rs πŸ”—

@@ -25,7 +25,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(paste);
 }
 
-pub fn visual_motion(motion: Motion, times: usize, cx: &mut WindowContext) {
+pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {

crates/welcome/src/base_keymap_picker.rs πŸ”—

@@ -141,7 +141,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
     ) -> gpui::AnyElement<Picker<Self>> {
         let theme = &theme::current(cx);
         let keymap_match = &self.matches[ix];
-        let style = theme.picker.item.style_for(mouse_state, selected);
+        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
 
         Label::new(keymap_match.string.clone(), style.label.clone())
             .with_highlights(keymap_match.positions.clone())

crates/workspace/src/dock.rs πŸ”—

@@ -249,7 +249,7 @@ impl Dock {
         }
     }
 
-    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
+    pub(crate) fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
         let subscriptions = [
             cx.observe(&panel, |_, _, cx| cx.notify()),
             cx.subscribe(&panel, |this, panel, event, cx| {
@@ -498,7 +498,9 @@ impl View for PanelButtons {
                     Stack::new()
                         .with_child(
                             MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
-                                let style = button_style.style_for(state, is_active);
+                                let style = button_style.in_state(is_active);
+
+                                let style = style.style_for(state);
                                 Flex::row()
                                     .with_child(
                                         Svg::new(view.icon_path(cx))
@@ -603,6 +605,7 @@ pub mod test {
     use super::*;
     use gpui::{ViewContext, WindowContext};
 
+    #[derive(Debug)]
     pub enum TestPanelEvent {
         PositionChanged,
         Activated,

crates/workspace/src/notifications.rs πŸ”—

@@ -291,7 +291,7 @@ pub mod simple_message_notification {
                         )
                         .with_child(
                             MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
-                                let style = theme.dismiss_button.style_for(state, false);
+                                let style = theme.dismiss_button.style_for(state);
                                 Svg::new("icons/x_mark_8.svg")
                                     .with_color(style.color)
                                     .constrained()
@@ -323,7 +323,7 @@ pub mod simple_message_notification {
                                 0,
                                 cx,
                                 |state, _| {
-                                    let style = theme.action_message.style_for(state, false);
+                                    let style = theme.action_message.style_for(state);
 
                                     Flex::row()
                                         .with_child(

crates/workspace/src/pane.rs πŸ”—

@@ -1,9 +1,10 @@
 mod dragged_item_receiver;
 
 use super::{ItemHandle, SplitDirection};
+pub use crate::toolbar::Toolbar;
 use crate::{
-    item::WeakItemHandle, notify_of_new_dock, toolbar::Toolbar, AutosaveSetting, Item,
-    NewCenterTerminal, NewFile, NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
+    item::WeakItemHandle, notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile,
+    NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
@@ -250,7 +251,7 @@ impl Pane {
                 pane: handle.clone(),
                 next_timestamp,
             }))),
-            toolbar: cx.add_view(|_| Toolbar::new(handle)),
+            toolbar: cx.add_view(|_| Toolbar::new(Some(handle))),
             tab_bar_context_menu: TabBarContextMenu {
                 kind: TabBarContextMenuKind::New,
                 handle: context_menu,
@@ -285,19 +286,27 @@ impl Pane {
                         pane.tab_bar_context_menu
                             .handle_if_kind(TabBarContextMenuKind::Split),
                     ))
-                    .with_child(Pane::render_tab_bar_button(
-                        2,
+                    .with_child({
+                        let icon_path;
+                        let tooltip_label;
                         if pane.is_zoomed() {
-                            "icons/minimize_8.svg"
+                            icon_path = "icons/minimize_8.svg";
+                            tooltip_label = "Zoom In".into();
                         } else {
-                            "icons/maximize_8.svg"
-                        },
-                        pane.is_zoomed(),
-                        Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))),
-                        cx,
-                        move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
-                        None,
-                    ))
+                            icon_path = "icons/maximize_8.svg";
+                            tooltip_label = "Zoom In".into();
+                        }
+
+                        Pane::render_tab_bar_button(
+                            2,
+                            icon_path,
+                            pane.is_zoomed(),
+                            Some((tooltip_label, Some(Box::new(ToggleZoom)))),
+                            cx,
+                            move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
+                            None,
+                        )
+                    })
                     .into_any()
             }),
         }
@@ -1112,7 +1121,7 @@ impl Pane {
             .get(self.active_item_index)
             .map(|item| item.as_ref());
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_active_pane_item(active_item, cx);
+            toolbar.set_active_item(active_item, cx);
         });
     }
 
@@ -1410,7 +1419,7 @@ impl Pane {
     pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
         index: usize,
         icon: &'static str,
-        active: bool,
+        is_active: bool,
         tooltip: Option<(String, Option<Box<dyn Action>>)>,
         cx: &mut ViewContext<Pane>,
         on_click: F,
@@ -1420,7 +1429,7 @@ impl Pane {
 
         let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
             let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
-            let style = theme.pane_button.style_for(mouse_state, active);
+            let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
             Svg::new(icon)
                 .with_color(style.color)
                 .constrained()
@@ -1602,7 +1611,7 @@ impl View for Pane {
         }
 
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.pane_focus_update(true, cx);
+            toolbar.focus_changed(true, cx);
         });
 
         if let Some(active_item) = self.active_item() {
@@ -1631,7 +1640,7 @@ impl View for Pane {
     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_focus = false;
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.pane_focus_update(false, cx);
+            toolbar.focus_changed(false, cx);
         });
         cx.notify();
     }

crates/workspace/src/toolbar.rs πŸ”—

@@ -38,7 +38,7 @@ trait ToolbarItemViewHandle {
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut WindowContext,
     ) -> ToolbarItemLocation;
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
+    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
     fn row_count(&self, cx: &WindowContext) -> usize;
 }
 
@@ -51,10 +51,10 @@ pub enum ToolbarItemLocation {
 }
 
 pub struct Toolbar {
-    active_pane_item: Option<Box<dyn ItemHandle>>,
+    active_item: Option<Box<dyn ItemHandle>>,
     hidden: bool,
     can_navigate: bool,
-    pane: WeakViewHandle<Pane>,
+    pane: Option<WeakViewHandle<Pane>>,
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
@@ -121,7 +121,7 @@ impl View for Toolbar {
         let pane = self.pane.clone();
         let mut enable_go_backward = false;
         let mut enable_go_forward = false;
-        if let Some(pane) = pane.upgrade(cx) {
+        if let Some(pane) = pane.and_then(|pane| pane.upgrade(cx)) {
             let pane = pane.read(cx);
             enable_go_backward = pane.can_navigate_backward();
             enable_go_forward = pane.can_navigate_forward();
@@ -143,19 +143,17 @@ impl View for Toolbar {
                 enable_go_backward,
                 spacing,
                 {
-                    let pane = pane.clone();
                     move |toolbar, cx| {
-                        if let Some(workspace) = toolbar
-                            .pane
-                            .upgrade(cx)
-                            .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                        if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
                         {
-                            let pane = pane.clone();
-                            cx.window_context().defer(move |cx| {
-                                workspace.update(cx, |workspace, cx| {
-                                    workspace.go_back(pane.clone(), cx).detach_and_log_err(cx);
-                                });
-                            })
+                            if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
+                                let pane = pane.downgrade();
+                                cx.window_context().defer(move |cx| {
+                                    workspace.update(cx, |workspace, cx| {
+                                        workspace.go_back(pane, cx).detach_and_log_err(cx);
+                                    });
+                                })
+                            }
                         }
                     }
                 },
@@ -171,21 +169,17 @@ impl View for Toolbar {
                 enable_go_forward,
                 spacing,
                 {
-                    let pane = pane.clone();
                     move |toolbar, cx| {
-                        if let Some(workspace) = toolbar
-                            .pane
-                            .upgrade(cx)
-                            .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                        if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
                         {
-                            let pane = pane.clone();
-                            cx.window_context().defer(move |cx| {
-                                workspace.update(cx, |workspace, cx| {
-                                    workspace
-                                        .go_forward(pane.clone(), cx)
-                                        .detach_and_log_err(cx);
-                                });
-                            });
+                            if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
+                                let pane = pane.downgrade();
+                                cx.window_context().defer(move |cx| {
+                                    workspace.update(cx, |workspace, cx| {
+                                        workspace.go_forward(pane, cx).detach_and_log_err(cx);
+                                    });
+                                })
+                            }
                         }
                     }
                 },
@@ -231,7 +225,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
 ) -> AnyElement<Toolbar> {
     MouseEventHandler::<A, _>::new(0, cx, |state, _| {
         let style = if enabled {
-            style.style_for(state, false)
+            style.style_for(state)
         } else {
             style.disabled_style()
         };
@@ -269,9 +263,9 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
 }
 
 impl Toolbar {
-    pub fn new(pane: WeakViewHandle<Pane>) -> Self {
+    pub fn new(pane: Option<WeakViewHandle<Pane>>) -> Self {
         Self {
-            active_pane_item: None,
+            active_item: None,
             pane,
             items: Default::default(),
             hidden: false,
@@ -288,7 +282,7 @@ impl Toolbar {
     where
         T: 'static + ToolbarItemView,
     {
-        let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
+        let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
         cx.subscribe(&item, |this, item, event, cx| {
             if let Some((_, current_location)) =
                 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
@@ -307,20 +301,16 @@ impl Toolbar {
         cx.notify();
     }
 
-    pub fn set_active_pane_item(
-        &mut self,
-        pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.active_pane_item = pane_item.map(|item| item.boxed_clone());
+    pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+        self.active_item = item.map(|item| item.boxed_clone());
         self.hidden = self
-            .active_pane_item
+            .active_item
             .as_ref()
             .map(|item| !item.show_toolbar(cx))
             .unwrap_or(false);
 
         for (toolbar_item, current_location) in self.items.iter_mut() {
-            let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
+            let new_location = toolbar_item.set_active_pane_item(item, cx);
             if new_location != *current_location {
                 *current_location = new_location;
                 cx.notify();
@@ -328,9 +318,9 @@ impl Toolbar {
         }
     }
 
-    pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
+    pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
         for (toolbar_item, _) in self.items.iter_mut() {
-            toolbar_item.pane_focus_update(pane_focused, cx);
+            toolbar_item.focus_changed(focused, cx);
         }
     }
 
@@ -364,7 +354,7 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
         })
     }
 
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
+    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
         self.update(cx, |this, cx| {
             this.pane_focus_update(pane_focused, cx);
             cx.notify();

crates/workspace/src/workspace.rs πŸ”—

@@ -140,9 +140,11 @@ pub struct OpenPaths {
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct ActivatePane(pub usize);
 
+#[derive(Deserialize)]
 pub struct Toast {
     id: usize,
     msg: Cow<'static, str>,
+    #[serde(skip)]
     on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 }
 
@@ -183,9 +185,9 @@ impl Clone for Toast {
     }
 }
 
-pub type WorkspaceId = i64;
+impl_actions!(workspace, [ActivatePane, Toast]);
 
-impl_actions!(workspace, [ActivatePane]);
+pub type WorkspaceId = i64;
 
 pub fn init_settings(cx: &mut AppContext) {
     settings::register::<WorkspaceSettings>(cx);
@@ -859,7 +861,10 @@ impl Workspace {
         &self.right_dock
     }
 
-    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
+    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    where
+        T::Event: std::fmt::Debug,
+    {
         let dock = match panel.position(cx) {
             DockPosition::Left => &self.left_dock,
             DockPosition::Bottom => &self.bottom_dock,
@@ -902,10 +907,11 @@ impl Workspace {
                     });
                 } else if T::should_zoom_in_on_event(event) {
                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
-                    if panel.has_focus(cx) {
-                        this.zoomed = Some(panel.downgrade().into_any());
-                        this.zoomed_position = Some(panel.read(cx).position(cx));
+                    if !panel.has_focus(cx) {
+                        cx.focus(&panel);
                     }
+                    this.zoomed = Some(panel.downgrade().into_any());
+                    this.zoomed_position = Some(panel.read(cx).position(cx));
                 } else if T::should_zoom_out_on_event(event) {
                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
                     if this.zoomed_position == Some(prev_position) {
@@ -1700,6 +1706,11 @@ impl Workspace {
         cx.notify();
     }
 
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
+        self.zoomed.and_then(|view| view.upgrade(cx))
+    }
+
     fn dismiss_zoomed_items_to_reveal(
         &mut self,
         dock_to_reveal: Option<DockPosition>,
@@ -2285,11 +2296,11 @@ impl Workspace {
         // (https://github.com/zed-industries/zed/issues/1290)
         let is_fullscreen = cx.window_is_fullscreen();
         let container_theme = if is_fullscreen {
-            let mut container_theme = theme.workspace.titlebar.container;
+            let mut container_theme = theme.titlebar.container;
             container_theme.padding.left = container_theme.padding.right;
             container_theme
         } else {
-            theme.workspace.titlebar.container
+            theme.titlebar.container
         };
 
         enum TitleBar {}
@@ -2309,7 +2320,7 @@ impl Workspace {
             }
         })
         .constrained()
-        .with_height(theme.workspace.titlebar.height)
+        .with_height(theme.titlebar.height)
         .into_any_named("titlebar")
     }
 
@@ -2754,7 +2765,7 @@ impl Workspace {
         let call = self.active_call()?;
         let room = call.read(cx).room()?.read(cx);
         let participant = room.remote_participant_for_peer_id(peer_id)?;
-        let track = participant.tracks.values().next()?.clone();
+        let track = participant.video_tracks.values().next()?.clone();
         let user = participant.user.clone();
 
         for item in pane.read(cx).items_of_type::<SharedScreen>() {

crates/xtask/Cargo.toml πŸ”—

@@ -0,0 +1,13 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2021"
+publish = false
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0"
+clap = {version = "4.0", features = ["derive"]}
+theme = {path = "../theme"}
+serde_json.workspace = true
+schemars.workspace = true

crates/xtask/src/cli.rs πŸ”—

@@ -0,0 +1,23 @@
+use clap::{Parser, Subcommand};
+use std::path::PathBuf;
+/// Common utilities for Zed developers.
+// For more information, see [matklad's repository README](https://github.com/matklad/cargo-xtask/)
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+#[command(propagate_version = true)]
+pub struct Cli {
+    #[command(subcommand)]
+    pub command: Commands,
+}
+
+/// Command to run.
+#[derive(Subcommand)]
+pub enum Commands {
+    /// Builds theme types for interop with Typescript.
+    BuildThemeTypes {
+        #[clap(short, long, default_value = "schemas")]
+        out_dir: PathBuf,
+        #[clap(short, long, default_value = "theme.json")]
+        file_name: PathBuf,
+    },
+}

crates/xtask/src/main.rs πŸ”—

@@ -0,0 +1,29 @@
+mod cli;
+
+use std::path::PathBuf;
+
+use anyhow::Result;
+use clap::Parser;
+use schemars::schema_for;
+use theme::Theme;
+
+fn build_themes(out_dir: PathBuf, file_name: PathBuf) -> Result<()> {
+    let theme = schema_for!(Theme);
+    let output = serde_json::to_string_pretty(&theme)?;
+
+    std::fs::create_dir(&out_dir)?;
+
+    let mut file_path = out_dir;
+    file_path.push(file_name);
+
+    std::fs::write(file_path, output)?;
+
+    Ok(())
+}
+
+fn main() -> Result<()> {
+    let args = cli::Cli::parse();
+    match args.command {
+        cli::Commands::BuildThemeTypes { out_dir, file_name } => build_themes(out_dir, file_name),
+    }
+}

crates/zed-actions/Cargo.toml πŸ”—

@@ -0,0 +1,10 @@
+[package]
+name = "zed-actions"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+gpui = { path = "../gpui" }

crates/zed-actions/src/lib.rs πŸ”—

@@ -0,0 +1,28 @@
+use gpui::actions;
+
+actions!(
+    zed,
+    [
+        About,
+        Hide,
+        HideOthers,
+        ShowAll,
+        Minimize,
+        Zoom,
+        ToggleFullScreen,
+        Quit,
+        DebugElements,
+        OpenLog,
+        OpenLicenses,
+        OpenTelemetryLog,
+        OpenKeymap,
+        OpenSettings,
+        OpenLocalSettings,
+        OpenDefaultSettings,
+        OpenDefaultKeymap,
+        IncreaseBufferFontSize,
+        DecreaseBufferFontSize,
+        ResetBufferFontSize,
+        ResetDatabase,
+    ]
+);

crates/zed/Cargo.toml πŸ”—

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.92.0"
+version = "0.94.0"
 publish = false
 
 [lib]
@@ -62,12 +62,11 @@ text = { path = "../text" }
 terminal_view = { path = "../terminal_view" }
 theme = { path = "../theme" }
 theme_selector = { path = "../theme_selector" }
-theme_testbench = { path = "../theme_testbench" }
 util = { path = "../util" }
 vim = { path = "../vim" }
 workspace = { path = "../workspace" }
 welcome = { path = "../welcome" }
-
+zed-actions = {path = "../zed-actions"}
 anyhow.workspace = true
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"

crates/zed/src/languages/heex/injections.scm πŸ”—

@@ -9,7 +9,5 @@
   (#set! combined)
 )
 
-; expressions live within HTML tags, and do not need to be combined
-;     <link href={ Routes.static_path(..) } />
 ((expression (expression_value) @content)
  (#set! language "elixir"))

crates/zed/src/main.rs πŸ”—

@@ -48,6 +48,7 @@ use util::{
     http::{self, HttpClient},
     paths::PathLikeWithPosition,
 };
+use uuid::Uuid;
 use welcome::{show_welcome_experience, FIRST_OPEN};
 
 use fs::RealFs;
@@ -68,9 +69,8 @@ fn main() {
     log::info!("========== starting zed ==========");
     let mut app = gpui::App::new(Assets).unwrap();
 
-    init_panic_hook(&app);
-
-    app.background();
+    let installation_id = app.background().block(installation_id()).ok();
+    init_panic_hook(&app, installation_id.clone());
 
     load_embedded_fonts(&app);
 
@@ -154,7 +154,6 @@ fn main() {
         search::init(cx);
         vim::init(cx);
         terminal_view::init(cx);
-        theme_testbench::init(cx);
         copilot::init(http.clone(), node_runtime, cx);
         ai::init(cx);
 
@@ -169,7 +168,7 @@ fn main() {
         })
         .detach();
 
-        client.telemetry().start();
+        client.telemetry().start(installation_id);
 
         let app_state = Arc::new(AppState {
             languages,
@@ -269,6 +268,22 @@ fn main() {
     });
 }
 
+async fn installation_id() -> Result<String> {
+    let legacy_key_name = "device_id";
+
+    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
+        Ok(installation_id)
+    } else {
+        let installation_id = Uuid::new_v4().to_string();
+
+        KEY_VALUE_STORE
+            .write_kvp(legacy_key_name.to_string(), installation_id.clone())
+            .await?;
+
+        Ok(installation_id)
+    }
+}
+
 fn open_urls(
     urls: Vec<String>,
     cli_connections_tx: &mpsc::UnboundedSender<(
@@ -372,6 +387,8 @@ struct Panic {
     os_version: Option<String>,
     architecture: String,
     panicked_on: u128,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    installation_id: Option<String>,
 }
 
 #[derive(Serialize)]
@@ -380,7 +397,7 @@ struct PanicRequest {
     token: String,
 }
 
-fn init_panic_hook(app: &App) {
+fn init_panic_hook(app: &App, installation_id: Option<String>) {
     let is_pty = stdout_is_a_pty();
     let platform = app.platform();
 
@@ -433,6 +450,7 @@ fn init_panic_hook(app: &App) {
                 .unwrap()
                 .as_millis(),
             backtrace,
+            installation_id: installation_id.clone(),
         };
 
         if is_pty {
@@ -871,6 +889,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
         ("Go to file", &file_finder::Toggle),
         ("Open command palette", &command_palette::Toggle),
         ("Open recent projects", &recent_projects::OpenRecent),
-        ("Change your settings", &zed::OpenSettings),
+        ("Change your settings", &zed_actions::OpenSettings),
     ]
 }

crates/zed/src/zed.rs πŸ”—

@@ -20,7 +20,6 @@ use feedback::{
 };
 use futures::{channel::mpsc, StreamExt};
 use gpui::{
-    actions,
     anyhow::{self, Result},
     geometry::vector::vec2f,
     impl_actions,
@@ -50,6 +49,7 @@ use workspace::{
     notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
     NewWindow, Workspace, WorkspaceSettings,
 };
+use zed_actions::*;
 
 #[derive(Deserialize, Clone, PartialEq)]
 pub struct OpenBrowser {
@@ -58,33 +58,6 @@ pub struct OpenBrowser {
 
 impl_actions!(zed, [OpenBrowser]);
 
-actions!(
-    zed,
-    [
-        About,
-        Hide,
-        HideOthers,
-        ShowAll,
-        Minimize,
-        Zoom,
-        ToggleFullScreen,
-        Quit,
-        DebugElements,
-        OpenLog,
-        OpenLicenses,
-        OpenTelemetryLog,
-        OpenKeymap,
-        OpenSettings,
-        OpenLocalSettings,
-        OpenDefaultSettings,
-        OpenDefaultKeymap,
-        IncreaseBufferFontSize,
-        DecreaseBufferFontSize,
-        ResetBufferFontSize,
-        ResetDatabase,
-    ]
-);
-
 pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
     cx.add_action(about);
     cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| {
@@ -361,15 +334,15 @@ pub fn initialize_workspace(
 
         let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
         let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
-        let assistant_panel = if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Stable {
-            None
-        } else {
-            Some(AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?)
-        };
-        let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?;
+        let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
+        let (project_panel, terminal_panel, assistant_panel) =
+            futures::try_join!(project_panel, terminal_panel, assistant_panel)?;
         workspace_handle.update(&mut cx, |workspace, cx| {
             let project_panel_position = project_panel.position(cx);
             workspace.add_panel(project_panel, cx);
+            workspace.add_panel(terminal_panel, cx);
+            workspace.add_panel(assistant_panel, cx);
+
             if !was_deserialized
                 && workspace
                     .project()
@@ -383,13 +356,7 @@ pub fn initialize_workspace(
             {
                 workspace.toggle_dock(project_panel_position, cx);
             }
-
             cx.focus_self();
-
-            workspace.add_panel(terminal_panel, cx);
-            if let Some(assistant_panel) = assistant_panel {
-                workspace.add_panel(assistant_panel, cx);
-            }
         })?;
         Ok(())
     })
@@ -741,8 +708,8 @@ mod tests {
     use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
     use fs::{FakeFs, Fs};
     use gpui::{
-        elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, AssetSource,
-        Element, Entity, TestAppContext, View, ViewHandle,
+        actions, elements::Empty, executor::Deterministic, Action, AnyElement, AppContext,
+        AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
     };
     use language::LanguageRegistry;
     use node_runtime::NodeRuntime;

docs/local-collaboration.md πŸ”—

@@ -0,0 +1,22 @@
+# Local Collaboration
+
+## Setting up the local collaboration server
+
+### Setting up for the first time?
+
+1. Make sure you have livekit installed (`brew install livekit`)
+1. Install [Postgres](https://postgresapp.com) and run it.
+1. Then, from the root of the repo, run `script/bootstrap`.
+
+### Have a db that is out of date? / Need to migrate?
+
+1. Make sure you have livekit installed (`brew install livekit`)
+1. Try `cd crates/collab && cargo run -- migrate` from the root of the repo.
+1. Run `script/seed-db`
+
+## Testing collab locally
+
+1. Run `foreman start` from the root of the repo.
+1. In another terminal run `script/start-local-collaboration`.
+1. Two copies of Zed will open. Add yourself as a contact in the one that is not you.
+1. Start a collaboration session as normal with any open project.

docs/zed/syntax-highlighting.md πŸ”—

@@ -0,0 +1,79 @@
+# Syntax Highlighting in Zed
+
+This doc is a work in progress!
+
+## Defining syntax highlighting rules
+
+We use tree-sitter queries to match certian properties to highlight.
+
+### Simple Example:
+
+```scheme
+(property_identifier) @property
+```
+
+```ts
+const font: FontFamily = {
+    weight: "normal",
+    underline: false,
+    italic: false,
+}
+```
+
+Match a property identifier and highlight it using the identifier `@property`. In the above example, `weight`, `underline`, and `italic` would be highlighted.
+
+### Complex example:
+
+```scheme
+(_
+  return_type: (type_annotation
+    [
+      (type_identifier) @type.return
+      (generic_type
+          name: (type_identifier) @type.return)
+    ]))
+```
+
+```ts
+function buildDefaultSyntax(colorScheme: ColorScheme): Partial<Syntax> {
+    // ...
+}
+```
+
+Match a function return type, and highlight the type using the identifier `@type.return`. In the above example, `Partial` would be highlighted.
+
+### Example - Typescript
+
+Here is an example portion of our `highlights.scm` for TypeScript:
+
+```scheme
+; crates/zed/src/languages/typescript/highlights.scm
+
+; Variables
+
+(identifier) @variable
+
+; Properties
+
+(property_identifier) @property
+
+; Function and method calls
+
+(call_expression
+  function: (identifier) @function)
+
+(call_expression
+  function: (member_expression
+    property: (property_identifier) @function.method))
+
+; Function and method definitions
+
+(function
+  name: (identifier) @function)
+(function_declaration
+  name: (identifier) @function)
+(method_definition
+  name: (property_identifier) @function.method)
+
+; ...
+```

script/build-theme-types πŸ”—

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+echo "running xtask"
+(cd crates/theme && cargo xtask build-theme-types)
+
+echo "updating theme packages"
+(cd styles && npm install)
+
+echo "building theme types"
+(cd styles && npm run build-types)

script/start-local-collaboration πŸ”—

@@ -54,5 +54,5 @@ sleep 0.5
 # Start the two Zed child processes. Open the given paths with the first instance.
 trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
 ZED_IMPERSONATE=${username_1} ZED_WINDOW_POSITION=${position_1} target/debug/Zed $@ &
-ZED_IMPERSONATE=${username_2} ZED_WINDOW_POSITION=${position_2} target/debug/Zed &
+SECOND=true ZED_IMPERSONATE=${username_2} ZED_WINDOW_POSITION=${position_2} target/debug/Zed &
 wait

styles/.zed/settings.json πŸ”—

@@ -0,0 +1,20 @@
+// Folder-specific settings
+//
+// For a full list of overridable settings, and general information on folder-specific settings,
+// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
+{
+    "languages": {
+        "TypeScript": {
+            "tab_size": 4
+        },
+        "TSX": {
+            "tab_size": 4
+        },
+        "JavaScript": {
+            "tab_size": 4
+        },
+        "JSON": {
+            "tab_size": 4
+        }
+    }
+}

styles/package-lock.json πŸ”—

@@ -1,7 +1,7 @@
 {
     "name": "styles",
     "version": "1.0.0",
-    "lockfileVersion": 2,
+    "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
@@ -17,10 +17,53 @@
                 "case-anything": "^2.1.10",
                 "chroma-js": "^2.4.2",
                 "deepmerge": "^4.3.0",
+                "json-schema-to-typescript": "^13.0.2",
                 "toml": "^3.0.0",
-                "ts-node": "^10.9.1"
+                "ts-deepmerge": "^6.0.3",
+                "ts-node": "^10.9.1",
+                "utility-types": "^3.10.0",
+                "vitest": "^0.32.0"
+            },
+            "devDependencies": {
+                "@vitest/coverage-v8": "^0.32.0"
+            }
+        },
+        "node_modules/@ampproject/remapping": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+            "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/gen-mapping": "^0.3.0",
+                "@jridgewell/trace-mapping": "^0.3.9"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@bcherny/json-schema-ref-parser": {
+            "version": "10.0.5-fork",
+            "resolved": "https://registry.npmjs.org/@bcherny/json-schema-ref-parser/-/json-schema-ref-parser-10.0.5-fork.tgz",
+            "integrity": "sha512-E/jKbPoca1tfUPj3iSbitDZTGnq6FUFjkH6L8U2oDwSuwK1WhnnVtCG7oFOTg/DDnyoXbQYUiUiGOibHqaGVnw==",
+            "dependencies": {
+                "@jsdevtools/ono": "^7.1.3",
+                "@types/json-schema": "^7.0.6",
+                "call-me-maybe": "^1.0.1",
+                "js-yaml": "^4.1.0"
+            },
+            "engines": {
+                "node": ">= 16"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/philsturgeon"
             }
         },
+        "node_modules/@bcoe/v8-coverage": {
+            "version": "0.2.3",
+            "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+            "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+            "dev": true
+        },
         "node_modules/@cspotcode/source-map-support": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -32,6 +75,44 @@
                 "node": ">=12"
             }
         },
+        "node_modules/@esbuild/darwin-arm64": {
+            "version": "0.17.19",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
+            "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+            "cpu": [
+                "arm64"
+            ],
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@istanbuljs/schema": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+            "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@jridgewell/gen-mapping": {
+            "version": "0.3.3",
+            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+            "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/set-array": "^1.0.1",
+                "@jridgewell/sourcemap-codec": "^1.4.10",
+                "@jridgewell/trace-mapping": "^0.3.9"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
         "node_modules/@jridgewell/resolve-uri": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@@ -40,6 +121,15 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/@jridgewell/set-array": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+            "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+            "dev": true,
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
         "node_modules/@jridgewell/sourcemap-codec": {
             "version": "1.4.14",
             "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
@@ -54,6 +144,11 @@
                 "@jridgewell/sourcemap-codec": "^1.4.10"
             }
         },
+        "node_modules/@jsdevtools/ono": {
+            "version": "7.1.3",
+            "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+            "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
+        },
         "node_modules/@tokens-studio/types": {
             "version": "0.2.3",
             "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz",
@@ -79,16 +174,153 @@
             "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
             "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
         },
+        "node_modules/@types/chai": {
+            "version": "4.3.5",
+            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz",
+            "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng=="
+        },
+        "node_modules/@types/chai-subset": {
+            "version": "1.3.3",
+            "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz",
+            "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==",
+            "dependencies": {
+                "@types/chai": "*"
+            }
+        },
         "node_modules/@types/chroma-js": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
             "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
         },
+        "node_modules/@types/glob": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+            "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+            "dependencies": {
+                "@types/minimatch": "*",
+                "@types/node": "*"
+            }
+        },
+        "node_modules/@types/istanbul-lib-coverage": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+            "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+            "dev": true
+        },
+        "node_modules/@types/json-schema": {
+            "version": "7.0.12",
+            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
+            "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
+        },
+        "node_modules/@types/lodash": {
+            "version": "4.14.195",
+            "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
+            "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
+        },
+        "node_modules/@types/minimatch": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+            "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA=="
+        },
         "node_modules/@types/node": {
             "version": "18.14.1",
             "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
             "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
         },
+        "node_modules/@types/prettier": {
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
+            "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA=="
+        },
+        "node_modules/@vitest/coverage-v8": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.32.0.tgz",
+            "integrity": "sha512-VXXlWq9X/NbsoP/l/CHLBjutsFFww1UY1qEhzGjn/DY7Tqe+z0Nu8XKc8im/XUAmjiWsh2XV7sy/F0IKAl4eaw==",
+            "dev": true,
+            "dependencies": {
+                "@ampproject/remapping": "^2.2.1",
+                "@bcoe/v8-coverage": "^0.2.3",
+                "istanbul-lib-coverage": "^3.2.0",
+                "istanbul-lib-report": "^3.0.0",
+                "istanbul-lib-source-maps": "^4.0.1",
+                "istanbul-reports": "^3.1.5",
+                "magic-string": "^0.30.0",
+                "picocolors": "^1.0.0",
+                "std-env": "^3.3.2",
+                "test-exclude": "^6.0.0",
+                "v8-to-istanbul": "^9.1.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            },
+            "peerDependencies": {
+                "vitest": ">=0.32.0 <1"
+            }
+        },
+        "node_modules/@vitest/expect": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.32.0.tgz",
+            "integrity": "sha512-VxVHhIxKw9Lux+O9bwLEEk2gzOUe93xuFHy9SzYWnnoYZFYg1NfBtnfnYWiJN7yooJ7KNElCK5YtA7DTZvtXtg==",
+            "dependencies": {
+                "@vitest/spy": "0.32.0",
+                "@vitest/utils": "0.32.0",
+                "chai": "^4.3.7"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/runner": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.32.0.tgz",
+            "integrity": "sha512-QpCmRxftHkr72xt5A08xTEs9I4iWEXIOCHWhQQguWOKE4QH7DXSKZSOFibuwEIMAD7G0ERvtUyQn7iPWIqSwmw==",
+            "dependencies": {
+                "@vitest/utils": "0.32.0",
+                "concordance": "^5.0.4",
+                "p-limit": "^4.0.0",
+                "pathe": "^1.1.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/snapshot": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.32.0.tgz",
+            "integrity": "sha512-yCKorPWjEnzpUxQpGlxulujTcSPgkblwGzAUEL+z01FTUg/YuCDZ8dxr9sHA08oO2EwxzHXNLjQKWJ2zc2a19Q==",
+            "dependencies": {
+                "magic-string": "^0.30.0",
+                "pathe": "^1.1.0",
+                "pretty-format": "^27.5.1"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/spy": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.32.0.tgz",
+            "integrity": "sha512-MruAPlM0uyiq3d53BkwTeShXY0rYEfhNGQzVO5GHBmmX3clsxcWp79mMnkOVcV244sNTeDcHbcPFWIjOI4tZvw==",
+            "dependencies": {
+                "tinyspy": "^2.1.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/utils": {
+            "version": "0.32.0",
+            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.32.0.tgz",
+            "integrity": "sha512-53yXunzx47MmbuvcOPpLaVljHaeSu1G2dHdmy7+9ngMnQIkBQcvwOcoclWFnxDMxFbnq8exAfh3aKSZaK71J5A==",
+            "dependencies": {
+                "concordance": "^5.0.4",
+                "loupe": "^2.3.6",
+                "pretty-format": "^27.5.1"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
         "node_modules/acorn": {
             "version": "8.8.2",
             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -108,11 +340,48 @@
                 "node": ">=0.4.0"
             }
         },
+        "node_modules/ansi-regex": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-styles": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+            "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/any-promise": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+            "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
+        },
         "node_modules/arg": {
             "version": "4.1.3",
             "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
             "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
         },
+        "node_modules/argparse": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+        },
+        "node_modules/assertion-error": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+            "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+            "engines": {
+                "node": "*"
+            }
+        },
         "node_modules/ayu": {
             "version": "8.0.1",
             "resolved": "https://registry.npmjs.org/ayu/-/ayu-8.0.1.tgz",
@@ -123,11 +392,43 @@
                 "nonenumerable": "^1.1.1"
             }
         },
+        "node_modules/balanced-match": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+        },
         "node_modules/bezier-easing": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
             "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
         },
+        "node_modules/blueimp-md5": {
+            "version": "2.19.0",
+            "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
+            "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
+        },
+        "node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/cac": {
+            "version": "6.7.14",
+            "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+            "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/call-me-maybe": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
+            "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
+        },
         "node_modules/case-anything": {
             "version": "2.1.10",
             "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
@@ -139,16 +440,132 @@
                 "url": "https://github.com/sponsors/mesqueeb"
             }
         },
+        "node_modules/chai": {
+            "version": "4.3.7",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
+            "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
+            "dependencies": {
+                "assertion-error": "^1.1.0",
+                "check-error": "^1.0.2",
+                "deep-eql": "^4.1.2",
+                "get-func-name": "^2.0.0",
+                "loupe": "^2.3.1",
+                "pathval": "^1.1.1",
+                "type-detect": "^4.0.5"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/check-error": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+            "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
+            "engines": {
+                "node": "*"
+            }
+        },
         "node_modules/chroma-js": {
             "version": "2.4.2",
             "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
             "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
         },
+        "node_modules/cli-color": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz",
+            "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==",
+            "dependencies": {
+                "d": "^1.0.1",
+                "es5-ext": "^0.10.61",
+                "es6-iterator": "^2.0.3",
+                "memoizee": "^0.4.15",
+                "timers-ext": "^0.1.7"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+        },
+        "node_modules/concordance": {
+            "version": "5.0.4",
+            "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz",
+            "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==",
+            "dependencies": {
+                "date-time": "^3.1.0",
+                "esutils": "^2.0.3",
+                "fast-diff": "^1.2.0",
+                "js-string-escape": "^1.0.1",
+                "lodash": "^4.17.15",
+                "md5-hex": "^3.0.1",
+                "semver": "^7.3.2",
+                "well-known-symbols": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14"
+            }
+        },
+        "node_modules/convert-source-map": {
+            "version": "1.9.0",
+            "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+            "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+            "dev": true
+        },
         "node_modules/create-require": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
             "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
         },
+        "node_modules/d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dependencies": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
+        "node_modules/date-time": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz",
+            "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==",
+            "dependencies": {
+                "time-zone": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/deep-eql": {
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+            "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+            "dependencies": {
+                "type-detect": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/deepmerge": {
             "version": "4.3.0",
             "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
@@ -165,228 +582,839 @@
                 "node": ">=0.3.1"
             }
         },
-        "node_modules/make-error": {
-            "version": "1.3.6",
-            "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
-            "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
+        "node_modules/es5-ext": {
+            "version": "0.10.62",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+            "hasInstallScript": true,
+            "dependencies": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
         },
-        "node_modules/nonenumerable": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/nonenumerable/-/nonenumerable-1.1.1.tgz",
-            "integrity": "sha512-ptUD9w9D8WqW6fuJJkZNCImkf+0vdbgUTbRK3i7jsy3olqtH96hYE6Q/S3Tx9NWbcB/ocAjYshXCAUP0lZ9B4Q=="
+        "node_modules/es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dependencies": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
         },
-        "node_modules/toml": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
-            "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
+        "node_modules/es6-symbol": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+            "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
+            "dependencies": {
+                "d": "^1.0.1",
+                "ext": "^1.1.2"
+            }
         },
-        "node_modules/ts-node": {
-            "version": "10.9.1",
-            "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
-            "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+        "node_modules/es6-weak-map": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
+            "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
             "dependencies": {
-                "@cspotcode/source-map-support": "^0.8.0",
-                "@tsconfig/node10": "^1.0.7",
-                "@tsconfig/node12": "^1.0.7",
-                "@tsconfig/node14": "^1.0.0",
-                "@tsconfig/node16": "^1.0.2",
-                "acorn": "^8.4.1",
-                "acorn-walk": "^8.1.1",
-                "arg": "^4.1.0",
-                "create-require": "^1.1.0",
-                "diff": "^4.0.1",
-                "make-error": "^1.1.1",
-                "v8-compile-cache-lib": "^3.0.1",
-                "yn": "3.1.1"
-            },
-            "bin": {
-                "ts-node": "dist/bin.js",
-                "ts-node-cwd": "dist/bin-cwd.js",
-                "ts-node-esm": "dist/bin-esm.js",
-                "ts-node-script": "dist/bin-script.js",
-                "ts-node-transpile-only": "dist/bin-transpile.js",
-                "ts-script": "dist/bin-script-deprecated.js"
-            },
-            "peerDependencies": {
-                "@swc/core": ">=1.2.50",
-                "@swc/wasm": ">=1.2.50",
-                "@types/node": "*",
-                "typescript": ">=2.7"
-            },
-            "peerDependenciesMeta": {
-                "@swc/core": {
-                    "optional": true
-                },
-                "@swc/wasm": {
-                    "optional": true
-                }
+                "d": "1",
+                "es5-ext": "^0.10.46",
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.1"
             }
         },
-        "node_modules/typescript": {
-            "version": "4.9.5",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-            "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-            "peer": true,
+        "node_modules/esbuild": {
+            "version": "0.17.19",
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
+            "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+            "hasInstallScript": true,
             "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
+                "esbuild": "bin/esbuild"
             },
             "engines": {
-                "node": ">=4.2.0"
+                "node": ">=12"
+            },
+            "optionalDependencies": {
+                "@esbuild/android-arm": "0.17.19",
+                "@esbuild/android-arm64": "0.17.19",
+                "@esbuild/android-x64": "0.17.19",
+                "@esbuild/darwin-arm64": "0.17.19",
+                "@esbuild/darwin-x64": "0.17.19",
+                "@esbuild/freebsd-arm64": "0.17.19",
+                "@esbuild/freebsd-x64": "0.17.19",
+                "@esbuild/linux-arm": "0.17.19",
+                "@esbuild/linux-arm64": "0.17.19",
+                "@esbuild/linux-ia32": "0.17.19",
+                "@esbuild/linux-loong64": "0.17.19",
+                "@esbuild/linux-mips64el": "0.17.19",
+                "@esbuild/linux-ppc64": "0.17.19",
+                "@esbuild/linux-riscv64": "0.17.19",
+                "@esbuild/linux-s390x": "0.17.19",
+                "@esbuild/linux-x64": "0.17.19",
+                "@esbuild/netbsd-x64": "0.17.19",
+                "@esbuild/openbsd-x64": "0.17.19",
+                "@esbuild/sunos-x64": "0.17.19",
+                "@esbuild/win32-arm64": "0.17.19",
+                "@esbuild/win32-ia32": "0.17.19",
+                "@esbuild/win32-x64": "0.17.19"
             }
         },
-        "node_modules/v8-compile-cache-lib": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
-            "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
-        },
-        "node_modules/yn": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
-            "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+        "node_modules/esutils": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
             "engines": {
-                "node": ">=6"
-            }
-        }
-    },
-    "dependencies": {
-        "@cspotcode/source-map-support": {
-            "version": "0.8.1",
-            "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
-            "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
-            "requires": {
-                "@jridgewell/trace-mapping": "0.3.9"
+                "node": ">=0.10.0"
             }
         },
-        "@jridgewell/resolve-uri": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
-            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
-        },
-        "@jridgewell/sourcemap-codec": {
-            "version": "1.4.14",
-            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-            "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+        "node_modules/event-emitter": {
+            "version": "0.3.5",
+            "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+            "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
+            "dependencies": {
+                "d": "1",
+                "es5-ext": "~0.10.14"
+            }
         },
-        "@jridgewell/trace-mapping": {
-            "version": "0.3.9",
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
-            "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
-            "requires": {
-                "@jridgewell/resolve-uri": "^3.0.3",
-                "@jridgewell/sourcemap-codec": "^1.4.10"
+        "node_modules/ext": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
+            "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
+            "dependencies": {
+                "type": "^2.7.2"
             }
         },
-        "@tokens-studio/types": {
-            "version": "0.2.3",
-            "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz",
-            "integrity": "sha512-2KN3V0JPf+Zh8aoVMwykJq29Lsi7vYgKGYBQ/zQ+FbDEmrH6T/Vwn8kG7cvbTmW1JAAvgxVxMIivgC9PmFelNA=="
+        "node_modules/ext/node_modules/type": {
+            "version": "2.7.2",
+            "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
+            "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
         },
-        "@tsconfig/node10": {
-            "version": "1.0.9",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
-            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
+        "node_modules/fast-diff": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+            "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
         },
-        "@tsconfig/node12": {
-            "version": "1.0.11",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
-            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
+        "node_modules/fs.realpath": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+            "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
         },
-        "@tsconfig/node14": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
-            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
+        "node_modules/fsevents": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+            "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+            "hasInstallScript": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+            }
         },
-        "@tsconfig/node16": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
-            "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
+        "node_modules/get-func-name": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+            "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+            "engines": {
+                "node": "*"
+            }
         },
-        "@types/chroma-js": {
-            "version": "2.4.0",
-            "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
-            "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
+        "node_modules/get-stdin": {
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+            "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
         },
-        "@types/node": {
-            "version": "18.14.1",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
-            "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
+        "node_modules/glob": {
+            "version": "7.2.3",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+            "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.1.1",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            },
+            "engines": {
+                "node": "*"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
         },
-        "acorn": {
-            "version": "8.8.2",
-            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-            "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
+        "node_modules/glob-promise": {
+            "version": "4.2.2",
+            "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz",
+            "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==",
+            "dependencies": {
+                "@types/glob": "^7.1.3"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "type": "individual",
+                "url": "https://github.com/sponsors/ahmadnassri"
+            },
+            "peerDependencies": {
+                "glob": "^7.1.6"
+            }
         },
-        "acorn-walk": {
-            "version": "8.2.0",
-            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
-            "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
+        "node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
         },
-        "arg": {
-            "version": "4.1.3",
-            "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
-            "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
+        "node_modules/html-escaper": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+            "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+            "dev": true
         },
-        "ayu": {
-            "version": "8.0.1",
-            "resolved": "https://registry.npmjs.org/ayu/-/ayu-8.0.1.tgz",
-            "integrity": "sha512-yuPZ2kZYQoYaPRQ/78F9rXDVx1rVGCJ1neBYithBoSprD6zPdIJdAKizUXG0jtTBu7nTFyAnVFFYuLnCS3cpDw==",
-            "requires": {
-                "@types/chroma-js": "^2.0.0",
-                "chroma-js": "^2.1.0",
-                "nonenumerable": "^1.1.1"
+        "node_modules/inflight": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+            "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+            "dependencies": {
+                "once": "^1.3.0",
+                "wrappy": "1"
             }
         },
-        "bezier-easing": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
-            "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
+        "node_modules/inherits": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
         },
-        "case-anything": {
-            "version": "2.1.10",
-            "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
-            "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ=="
+        "node_modules/is-extglob": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+            "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
         },
-        "chroma-js": {
-            "version": "2.4.2",
-            "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
-            "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
+        "node_modules/is-glob": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+            "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+            "dependencies": {
+                "is-extglob": "^2.1.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
         },
-        "create-require": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
-            "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
+        "node_modules/is-promise": {
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+            "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
         },
-        "deepmerge": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
-            "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
+        "node_modules/istanbul-lib-coverage": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
+            "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
         },
-        "diff": {
-            "version": "4.0.2",
-            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
+        "node_modules/istanbul-lib-report": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+            "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+            "dev": true,
+            "dependencies": {
+                "istanbul-lib-coverage": "^3.0.0",
+                "make-dir": "^3.0.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/istanbul-lib-source-maps": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+            "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+            "dev": true,
+            "dependencies": {
+                "debug": "^4.1.1",
+                "istanbul-lib-coverage": "^3.0.0",
+                "source-map": "^0.6.1"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/istanbul-reports": {
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
+            "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+            "dev": true,
+            "dependencies": {
+                "html-escaper": "^2.0.0",
+                "istanbul-lib-report": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/js-string-escape": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
+            "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==",
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
+        "node_modules/js-yaml": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+            "dependencies": {
+                "argparse": "^2.0.1"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/json-schema-to-typescript": {
+            "version": "13.0.2",
+            "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-13.0.2.tgz",
+            "integrity": "sha512-TCaEVW4aI2FmMQe7f98mvr3/oiVmXEC1xZjkTZ9L/BSoTXFlC7p64mD5AD2d8XWycNBQZUnHwXL5iVXt1HWwNQ==",
+            "dependencies": {
+                "@bcherny/json-schema-ref-parser": "10.0.5-fork",
+                "@types/json-schema": "^7.0.11",
+                "@types/lodash": "^4.14.182",
+                "@types/prettier": "^2.6.1",
+                "cli-color": "^2.0.2",
+                "get-stdin": "^8.0.0",
+                "glob": "^7.1.6",
+                "glob-promise": "^4.2.2",
+                "is-glob": "^4.0.3",
+                "lodash": "^4.17.21",
+                "minimist": "^1.2.6",
+                "mkdirp": "^1.0.4",
+                "mz": "^2.7.0",
+                "prettier": "^2.6.2"
+            },
+            "bin": {
+                "json2ts": "dist/src/cli.js"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
+        "node_modules/jsonc-parser": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
+            "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
+        },
+        "node_modules/local-pkg": {
+            "version": "0.4.3",
+            "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz",
+            "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
+            "engines": {
+                "node": ">=14"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/antfu"
+            }
+        },
+        "node_modules/lodash": {
+            "version": "4.17.21",
+            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+        },
+        "node_modules/loupe": {
+            "version": "2.3.6",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
+            "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
+            "dependencies": {
+                "get-func-name": "^2.0.0"
+            }
+        },
+        "node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/lru-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
+            "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==",
+            "dependencies": {
+                "es5-ext": "~0.10.2"
+            }
+        },
+        "node_modules/magic-string": {
+            "version": "0.30.0",
+            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
+            "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+            "dependencies": {
+                "@jridgewell/sourcemap-codec": "^1.4.13"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/make-dir": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+            "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+            "dev": true,
+            "dependencies": {
+                "semver": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
         },
-        "make-error": {
+        "node_modules/make-dir/node_modules/semver": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+            "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+            "dev": true,
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/make-error": {
             "version": "1.3.6",
             "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
         },
-        "nonenumerable": {
+        "node_modules/md5-hex": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz",
+            "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==",
+            "dependencies": {
+                "blueimp-md5": "^2.10.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/memoizee": {
+            "version": "0.4.15",
+            "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
+            "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==",
+            "dependencies": {
+                "d": "^1.0.1",
+                "es5-ext": "^0.10.53",
+                "es6-weak-map": "^2.0.3",
+                "event-emitter": "^0.3.5",
+                "is-promise": "^2.2.2",
+                "lru-queue": "^0.1.0",
+                "next-tick": "^1.1.0",
+                "timers-ext": "^0.1.7"
+            }
+        },
+        "node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/minimist": {
+            "version": "1.2.8",
+            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+            "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/mkdirp": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+            "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+            "bin": {
+                "mkdirp": "bin/cmd.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/mlly": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.3.0.tgz",
+            "integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==",
+            "dependencies": {
+                "acorn": "^8.8.2",
+                "pathe": "^1.1.0",
+                "pkg-types": "^1.0.3",
+                "ufo": "^1.1.2"
+            }
+        },
+        "node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/mz": {
+            "version": "2.7.0",
+            "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+            "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+            "dependencies": {
+                "any-promise": "^1.0.0",
+                "object-assign": "^4.0.1",
+                "thenify-all": "^1.0.0"
+            }
+        },
+        "node_modules/nanoid": {
+            "version": "3.3.6",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+            "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "bin": {
+                "nanoid": "bin/nanoid.cjs"
+            },
+            "engines": {
+                "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+            }
+        },
+        "node_modules/next-tick": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+            "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
+        },
+        "node_modules/nonenumerable": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/nonenumerable/-/nonenumerable-1.1.1.tgz",
             "integrity": "sha512-ptUD9w9D8WqW6fuJJkZNCImkf+0vdbgUTbRK3i7jsy3olqtH96hYE6Q/S3Tx9NWbcB/ocAjYshXCAUP0lZ9B4Q=="
         },
-        "toml": {
+        "node_modules/object-assign": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+            "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+            "dependencies": {
+                "wrappy": "1"
+            }
+        },
+        "node_modules/p-limit": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+            "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+            "dependencies": {
+                "yocto-queue": "^1.0.0"
+            },
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/path-is-absolute": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/pathe": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
+            "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q=="
+        },
+        "node_modules/pathval": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+            "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/picocolors": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+            "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+        },
+        "node_modules/pkg-types": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
+            "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+            "dependencies": {
+                "jsonc-parser": "^3.2.0",
+                "mlly": "^1.2.0",
+                "pathe": "^1.1.0"
+            }
+        },
+        "node_modules/postcss": {
+            "version": "8.4.24",
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
+            "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+            "funding": [
+                {
+                    "type": "opencollective",
+                    "url": "https://opencollective.com/postcss/"
+                },
+                {
+                    "type": "tidelift",
+                    "url": "https://tidelift.com/funding/github/npm/postcss"
+                },
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "dependencies": {
+                "nanoid": "^3.3.6",
+                "picocolors": "^1.0.0",
+                "source-map-js": "^1.0.2"
+            },
+            "engines": {
+                "node": "^10 || ^12 || >=14"
+            }
+        },
+        "node_modules/prettier": {
+            "version": "2.8.8",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+            "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+            "bin": {
+                "prettier": "bin-prettier.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            },
+            "funding": {
+                "url": "https://github.com/prettier/prettier?sponsor=1"
+            }
+        },
+        "node_modules/pretty-format": {
+            "version": "27.5.1",
+            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+            "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+            "dependencies": {
+                "ansi-regex": "^5.0.1",
+                "ansi-styles": "^5.0.0",
+                "react-is": "^17.0.1"
+            },
+            "engines": {
+                "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+            }
+        },
+        "node_modules/react-is": {
+            "version": "17.0.2",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+            "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+        },
+        "node_modules/rollup": {
+            "version": "3.25.1",
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
+            "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
+            "bin": {
+                "rollup": "dist/bin/rollup"
+            },
+            "engines": {
+                "node": ">=14.18.0",
+                "npm": ">=8.0.0"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.2"
+            }
+        },
+        "node_modules/semver": {
+            "version": "7.5.2",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
+            "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/siginfo": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+            "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
+        },
+        "node_modules/source-map": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/source-map-js": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+            "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/stackback": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+            "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="
+        },
+        "node_modules/std-env": {
+            "version": "3.3.3",
+            "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz",
+            "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg=="
+        },
+        "node_modules/strip-literal": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.1.tgz",
+            "integrity": "sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==",
+            "dependencies": {
+                "acorn": "^8.8.2"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/antfu"
+            }
+        },
+        "node_modules/supports-color": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+            "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/test-exclude": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+            "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+            "dev": true,
+            "dependencies": {
+                "@istanbuljs/schema": "^0.1.2",
+                "glob": "^7.1.4",
+                "minimatch": "^3.0.4"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/thenify": {
+            "version": "3.3.1",
+            "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+            "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+            "dependencies": {
+                "any-promise": "^1.0.0"
+            }
+        },
+        "node_modules/thenify-all": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+            "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+            "dependencies": {
+                "thenify": ">= 3.1.0 < 4"
+            },
+            "engines": {
+                "node": ">=0.8"
+            }
+        },
+        "node_modules/time-zone": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz",
+            "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/timers-ext": {
+            "version": "0.1.7",
+            "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
+            "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
+            "dependencies": {
+                "es5-ext": "~0.10.46",
+                "next-tick": "1"
+            }
+        },
+        "node_modules/tinybench": {
+            "version": "2.5.0",
+            "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz",
+            "integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA=="
+        },
+        "node_modules/tinypool": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz",
+            "integrity": "sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/tinyspy": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz",
+            "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/toml": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
             "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
         },
-        "ts-node": {
+        "node_modules/ts-deepmerge": {
+            "version": "6.0.3",
+            "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-6.0.3.tgz",
+            "integrity": "sha512-MBBJL0UK/mMnZRONMz4J1CRu5NsGtsh+gR1nkn8KLE9LXo/PCzeHhQduhNary8m5/m9ryOOyFwVKxq81cPlaow==",
+            "engines": {
+                "node": ">=14.13.1"
+            }
+        },
+        "node_modules/ts-node": {
             "version": "10.9.1",
             "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
             "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
-            "requires": {
+            "dependencies": {
                 "@cspotcode/source-map-support": "^0.8.0",
                 "@tsconfig/node10": "^1.0.7",
                 "@tsconfig/node12": "^1.0.7",

styles/package.json πŸ”—

@@ -6,7 +6,9 @@
     "scripts": {
         "build": "ts-node ./src/buildThemes.ts",
         "build-licenses": "ts-node ./src/buildLicenses.ts",
-        "build-tokens": "ts-node ./src/buildTokens.ts"
+        "build-tokens": "ts-node ./src/buildTokens.ts",
+        "build-types": "ts-node ./src/buildTypes.ts",
+        "test": "vitest"
     },
     "author": "",
     "license": "ISC",
@@ -19,13 +21,20 @@
         "case-anything": "^2.1.10",
         "chroma-js": "^2.4.2",
         "deepmerge": "^4.3.0",
+        "json-schema-to-typescript": "^13.0.2",
         "toml": "^3.0.0",
-        "ts-node": "^10.9.1"
+        "ts-deepmerge": "^6.0.3",
+        "ts-node": "^10.9.1",
+        "utility-types": "^3.10.0",
+        "vitest": "^0.32.0"
     },
     "prettier": {
         "semi": false,
         "printWidth": 80,
         "htmlWhitespaceSensitivity": "strict",
         "tabWidth": 4
+    },
+    "devDependencies": {
+        "@vitest/coverage-v8": "^0.32.0"
     }
 }

styles/src/buildTokens.ts πŸ”—

@@ -1,13 +1,13 @@
-import * as fs from "fs";
-import * as path from "path";
-import { ColorScheme, createColorScheme } from "./common";
-import { themes } from "./themes";
-import { slugify } from "./utils/slugify";
-import { colorSchemeTokens } from "./theme/tokens/colorScheme";
+import * as fs from "fs"
+import * as path from "path"
+import { ColorScheme, createColorScheme } from "./common"
+import { themes } from "./themes"
+import { slugify } from "./utils/slugify"
+import { colorSchemeTokens } from "./theme/tokens/colorScheme"
 
-const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens");
-const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json");
-const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json");
+const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens")
+const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json")
+const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json")
 
 function clearTokens(tokensDirectory: string) {
     if (!fs.existsSync(tokensDirectory)) {
@@ -22,64 +22,66 @@ function clearTokens(tokensDirectory: string) {
 }
 
 type TokenSet = {
-    id: string;
-    name: string;
-    selectedTokenSets: { [key: string]: "enabled" };
-};
+    id: string
+    name: string
+    selectedTokenSets: { [key: string]: "enabled" }
+}
 
-function buildTokenSetOrder(colorSchemes: ColorScheme[]): { tokenSetOrder: string[] } {
-    const tokenSetOrder: string[] = colorSchemes.map(
-        (scheme) => scheme.name.toLowerCase().replace(/\s+/g, "_")
-    );
-    return { tokenSetOrder };
+function buildTokenSetOrder(colorSchemes: ColorScheme[]): {
+    tokenSetOrder: string[]
+} {
+    const tokenSetOrder: string[] = colorSchemes.map((scheme) =>
+        scheme.name.toLowerCase().replace(/\s+/g, "_")
+    )
+    return { tokenSetOrder }
 }
 
 function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] {
     const themesIndex: TokenSet[] = colorSchemes.map((scheme, index) => {
         const id = `${scheme.isLight ? "light" : "dark"}_${scheme.name
             .toLowerCase()
-            .replace(/\s+/g, "_")}_${index}`;
-        const selectedTokenSets: { [key: string]: "enabled" } = {};
-        const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_");
-        selectedTokenSets[tokenSet] = "enabled";
+            .replace(/\s+/g, "_")}_${index}`
+        const selectedTokenSets: { [key: string]: "enabled" } = {}
+        const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_")
+        selectedTokenSets[tokenSet] = "enabled"
 
         return {
             id,
             name: `${scheme.name} - ${scheme.isLight ? "Light" : "Dark"}`,
             selectedTokenSets,
-        };
-    });
+        }
+    })
 
-    return themesIndex;
+    return themesIndex
 }
 
 function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) {
-    clearTokens(tokensDirectory);
+    clearTokens(tokensDirectory)
 
     for (const colorScheme of colorSchemes) {
-        const fileName = slugify(colorScheme.name) + ".json";
-        const tokens = colorSchemeTokens(colorScheme);
-        const tokensJSON = JSON.stringify(tokens, null, 2);
-        const outPath = path.join(tokensDirectory, fileName);
-        fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 });
-        console.log(`- ${outPath} created`);
+        const fileName = slugify(colorScheme.name) + ".json"
+        const tokens = colorSchemeTokens(colorScheme)
+        const tokensJSON = JSON.stringify(tokens, null, 2)
+        const outPath = path.join(tokensDirectory, fileName)
+        fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 })
+        console.log(`- ${outPath} created`)
     }
 
-    const themeIndexData = buildThemesIndex(colorSchemes);
+    const themeIndexData = buildThemesIndex(colorSchemes)
 
-    const themesJSON = JSON.stringify(themeIndexData, null, 2);
-    fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 });
-    console.log(`- ${TOKENS_FILE} created`);
+    const themesJSON = JSON.stringify(themeIndexData, null, 2)
+    fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 })
+    console.log(`- ${TOKENS_FILE} created`)
 
-    const tokenSetOrderData = buildTokenSetOrder(colorSchemes);
+    const tokenSetOrderData = buildTokenSetOrder(colorSchemes)
 
-    const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2);
-    fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 });
-    console.log(`- ${METADATA_FILE} created`);
+    const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2)
+    fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 })
+    console.log(`- ${METADATA_FILE} created`)
 }
 
 const colorSchemes: ColorScheme[] = themes.map((theme) =>
     createColorScheme(theme)
-);
+)
 
-writeTokens(colorSchemes, TOKENS_DIRECTORY);
+writeTokens(colorSchemes, TOKENS_DIRECTORY)

styles/src/buildTypes.ts πŸ”—

@@ -0,0 +1,64 @@
+import * as fs from "fs/promises"
+import * as fsSync from "fs"
+import * as path from "path"
+import { compile } from "json-schema-to-typescript"
+
+const BANNER = `/*
+* This file is autogenerated
+*/\n\n`
+const dirname = __dirname
+
+async function main() {
+    let schemasPath = path.join(dirname, "../../", "crates/theme/schemas")
+    let schemaFiles = (await fs.readdir(schemasPath)).filter((x) =>
+        x.endsWith(".json")
+    )
+
+    let compiledTypes = new Set()
+
+    for (let filename of schemaFiles) {
+        let filePath = path.join(schemasPath, filename)
+        const fileContents = await fs.readFile(filePath)
+        let schema = JSON.parse(fileContents.toString())
+        let compiled = await compile(schema, schema.title, {
+            bannerComment: "",
+        })
+        let eachType = compiled.split("export")
+        for (let type of eachType) {
+            if (!type) {
+                continue
+            }
+            compiledTypes.add("export " + type.trim())
+        }
+    }
+
+    let output = BANNER + Array.from(compiledTypes).join("\n\n")
+    let outputPath = path.join(dirname, "../../styles/src/types/zed.ts")
+
+    try {
+        let existing = await fs.readFile(outputPath)
+        if (existing.toString() == output) {
+            // Skip writing if it hasn't changed
+            console.log("Schemas are up to date")
+            return
+        }
+    } catch (e) {
+        // It's fine if there's no output from a previous run.
+        // @ts-ignore
+        if (e.code !== "ENOENT") {
+            throw e
+        }
+    }
+
+    const typesDic = path.dirname(outputPath)
+    if (!fsSync.existsSync(typesDic)) {
+        await fs.mkdir(typesDic)
+    }
+    await fs.writeFile(outputPath, output)
+    console.log(`Wrote Typescript types to ${outputPath}`)
+}
+
+main().catch((e) => {
+    console.error(e)
+    process.exit(1)
+})

styles/src/component/icon_button.ts πŸ”—

@@ -0,0 +1,86 @@
+import { ColorScheme } from "../common"
+import { interactive, toggleable } from "../element"
+import { background, foreground } from "../styleTree/components"
+
+export type Margin = {
+    top: number
+    bottom: number
+    left: number
+    right: number
+}
+
+interface IconButtonOptions {
+    layer?:
+        | ColorScheme["lowest"]
+        | ColorScheme["middle"]
+        | ColorScheme["highest"]
+    color?: keyof ColorScheme["lowest"]
+    margin?: Partial<Margin>
+}
+
+type ToggleableIconButtonOptions = IconButtonOptions & {
+    active_color?: keyof ColorScheme["lowest"]
+}
+
+export function icon_button(
+    theme: ColorScheme,
+    { color, margin, layer }: IconButtonOptions
+) {
+    if (!color) color = "base"
+
+    const m = {
+        top: margin?.top ?? 0,
+        bottom: margin?.bottom ?? 0,
+        left: margin?.left ?? 0,
+        right: margin?.right ?? 0,
+    }
+
+    return interactive({
+        base: {
+            corner_radius: 6,
+            padding: {
+                top: 2,
+                bottom: 2,
+                left: 4,
+                right: 4,
+            },
+            margin: m,
+            icon_width: 14,
+            icon_height: 14,
+            button_width: 20,
+            button_height: 16,
+        },
+        state: {
+            default: {
+                background: background(layer ?? theme.lowest, color),
+                color: foreground(layer ?? theme.lowest, color),
+            },
+            hovered: {
+                background: background(layer ?? theme.lowest, color, "hovered"),
+                color: foreground(layer ?? theme.lowest, color, "hovered"),
+            },
+            clicked: {
+                background: background(layer ?? theme.lowest, color, "pressed"),
+                color: foreground(layer ?? theme.lowest, color, "pressed"),
+            },
+        },
+    })
+}
+
+export function toggleable_icon_button(
+    theme: ColorScheme,
+    { color, active_color, margin }: ToggleableIconButtonOptions
+) {
+    if (!color) color = "base"
+
+    return toggleable({
+        state: {
+            inactive: icon_button(theme, { color, margin }),
+            active: icon_button(theme, {
+                color: active_color ? active_color : color,
+                margin,
+                layer: theme.middle,
+            }),
+        },
+    })
+}

styles/src/component/text_button.ts πŸ”—

@@ -0,0 +1,90 @@
+import { ColorScheme } from "../common"
+import { interactive, toggleable } from "../element"
+import {
+    TextProperties,
+    background,
+    foreground,
+    text,
+} from "../styleTree/components"
+import { Margin } from "./icon_button"
+
+interface TextButtonOptions {
+    layer?:
+        | ColorScheme["lowest"]
+        | ColorScheme["middle"]
+        | ColorScheme["highest"]
+    color?: keyof ColorScheme["lowest"]
+    margin?: Partial<Margin>
+    text_properties?: TextProperties
+}
+
+type ToggleableTextButtonOptions = TextButtonOptions & {
+    active_color?: keyof ColorScheme["lowest"]
+}
+
+export function text_button(
+    theme: ColorScheme,
+    { color, layer, margin, text_properties }: TextButtonOptions
+) {
+    if (!color) color = "base"
+
+    const text_options: TextProperties = {
+        size: "xs",
+        weight: "normal",
+        ...text_properties,
+    }
+
+    const m = {
+        top: margin?.top ?? 0,
+        bottom: margin?.bottom ?? 0,
+        left: margin?.left ?? 0,
+        right: margin?.right ?? 0,
+    }
+
+    return interactive({
+        base: {
+            corner_radius: 6,
+            padding: {
+                top: 1,
+                bottom: 1,
+                left: 6,
+                right: 6,
+            },
+            margin: m,
+            button_height: 22,
+            ...text(layer ?? theme.lowest, "sans", color, text_options),
+        },
+        state: {
+            default: {
+                background: background(layer ?? theme.lowest, color),
+                color: foreground(layer ?? theme.lowest, color),
+            },
+            hovered: {
+                background: background(layer ?? theme.lowest, color, "hovered"),
+                color: foreground(layer ?? theme.lowest, color, "hovered"),
+            },
+            clicked: {
+                background: background(layer ?? theme.lowest, color, "pressed"),
+                color: foreground(layer ?? theme.lowest, color, "pressed"),
+            },
+        },
+    })
+}
+
+export function toggleable_text_button(
+    theme: ColorScheme,
+    { color, active_color, margin }: ToggleableTextButtonOptions
+) {
+    if (!color) color = "base"
+
+    return toggleable({
+        state: {
+            inactive: text_button(theme, { color, margin }),
+            active: text_button(theme, {
+                color: active_color ? active_color : color,
+                margin,
+                layer: theme.middle,
+            }),
+        },
+    })
+}

styles/src/element/index.ts πŸ”—

@@ -0,0 +1,4 @@
+import { interactive, Interactive } from "./interactive"
+import { toggleable } from "./toggle"
+
+export { interactive, Interactive, toggleable }

styles/src/element/interactive.test.ts πŸ”—

@@ -0,0 +1,56 @@
+import {
+    NOT_ENOUGH_STATES_ERROR,
+    NO_DEFAULT_OR_BASE_ERROR,
+    interactive,
+} from "./interactive"
+import { describe, it, expect } from "vitest"
+
+describe("interactive", () => {
+    it("creates an Interactive<Element> with base properties and states", () => {
+        const result = interactive({
+            base: { fontSize: 10, color: "#FFFFFF" },
+            state: {
+                hovered: { color: "#EEEEEE" },
+                clicked: { color: "#CCCCCC" },
+            },
+        })
+
+        expect(result).toEqual({
+            default: { color: "#FFFFFF", fontSize: 10 },
+            hovered: { color: "#EEEEEE", fontSize: 10 },
+            clicked: { color: "#CCCCCC", fontSize: 10 },
+        })
+    })
+
+    it("creates an Interactive<Element> with no base properties", () => {
+        const result = interactive({
+            state: {
+                default: { color: "#FFFFFF", fontSize: 10 },
+                hovered: { color: "#EEEEEE" },
+                clicked: { color: "#CCCCCC" },
+            },
+        })
+
+        expect(result).toEqual({
+            default: { color: "#FFFFFF", fontSize: 10 },
+            hovered: { color: "#EEEEEE", fontSize: 10 },
+            clicked: { color: "#CCCCCC", fontSize: 10 },
+        })
+    })
+
+    it("throws error when both default and base are missing", () => {
+        const state = {
+            hovered: { color: "blue" },
+        }
+
+        expect(() => interactive({ state })).toThrow(NO_DEFAULT_OR_BASE_ERROR)
+    })
+
+    it("throws error when no other state besides default is present", () => {
+        const state = {
+            default: { fontSize: 10 },
+        }
+
+        expect(() => interactive({ state })).toThrow(NOT_ENOUGH_STATES_ERROR)
+    })
+})

styles/src/element/interactive.ts πŸ”—

@@ -0,0 +1,97 @@
+import merge from "ts-deepmerge"
+import { DeepPartial } from "utility-types"
+
+export type InteractiveState =
+    | "default"
+    | "hovered"
+    | "clicked"
+    | "selected"
+    | "disabled"
+
+export type Interactive<T> = {
+    default: T
+    hovered?: T
+    clicked?: T
+    selected?: T
+    disabled?: T
+}
+
+export const NO_DEFAULT_OR_BASE_ERROR =
+    "An interactive object must have a default state, or a base property."
+export const NOT_ENOUGH_STATES_ERROR =
+    "An interactive object must have a default and at least one other state."
+
+interface InteractiveProps<T> {
+    base?: T
+    state: Partial<Record<InteractiveState, DeepPartial<T>>>
+}
+
+/**
+ * Helper function for creating Interactive<T> objects that works with Toggle<T>-like behavior.
+ * It takes a default object to be used as the value for `default` field and fills out other fields
+ * with fields from either `base` or from the `state` object which contains values for specific states.
+ * Notably, it does not touch `hover`, `clicked`, `selected` and `disabled` states if there are no modifications for them.
+ *
+ * @param defaultObj Object to be used as the value for the `default` field.
+ * @param base Optional object containing base fields to be included in the resulting object.
+ * @param state Object containing optional modified fields to be included in the resulting object for each state.
+ * @returns Interactive<T> object with fields from `base` and `state`.
+ */
+export function interactive<T extends Object>({
+    base,
+    state,
+}: InteractiveProps<T>): Interactive<T> {
+    if (!base && !state.default) throw new Error(NO_DEFAULT_OR_BASE_ERROR)
+
+    let defaultState: T
+
+    if (state.default && base) {
+        defaultState = merge(base, state.default) as T
+    } else {
+        defaultState = base ? base : (state.default as T)
+    }
+
+    let interactiveObj: Interactive<T> = {
+        default: defaultState,
+    }
+
+    let stateCount = 0
+
+    if (state.hovered !== undefined) {
+        interactiveObj.hovered = merge(
+            interactiveObj.default,
+            state.hovered
+        ) as T
+        stateCount++
+    }
+
+    if (state.clicked !== undefined) {
+        interactiveObj.clicked = merge(
+            interactiveObj.default,
+            state.clicked
+        ) as T
+        stateCount++
+    }
+
+    if (state.selected !== undefined) {
+        interactiveObj.selected = merge(
+            interactiveObj.default,
+            state.selected
+        ) as T
+        stateCount++
+    }
+
+    if (state.disabled !== undefined) {
+        interactiveObj.disabled = merge(
+            interactiveObj.default,
+            state.disabled
+        ) as T
+        stateCount++
+    }
+
+    if (stateCount < 1) {
+        throw new Error(NOT_ENOUGH_STATES_ERROR)
+    }
+
+    return interactiveObj
+}

styles/src/element/toggle.test.ts πŸ”—

@@ -0,0 +1,52 @@
+import {
+    NO_ACTIVE_ERROR,
+    NO_INACTIVE_OR_BASE_ERROR,
+    toggleable,
+} from "./toggle"
+import { describe, it, expect } from "vitest"
+
+describe("toggleable", () => {
+    it("creates a Toggleable<Element> with base properties and states", () => {
+        const result = toggleable({
+            base: { background: "#000000", color: "#CCCCCC" },
+            state: {
+                active: { color: "#FFFFFF" },
+            },
+        })
+
+        expect(result).toEqual({
+            inactive: { background: "#000000", color: "#CCCCCC" },
+            active: { background: "#000000", color: "#FFFFFF" },
+        })
+    })
+
+    it("creates a Toggleable<Element> with no base properties", () => {
+        const result = toggleable({
+            state: {
+                inactive: { background: "#000000", color: "#CCCCCC" },
+                active: { background: "#000000", color: "#FFFFFF" },
+            },
+        })
+
+        expect(result).toEqual({
+            inactive: { background: "#000000", color: "#CCCCCC" },
+            active: { background: "#000000", color: "#FFFFFF" },
+        })
+    })
+
+    it("throws error when both inactive and base are missing", () => {
+        const state = {
+            active: { background: "#000000", color: "#FFFFFF" },
+        }
+
+        expect(() => toggleable({ state })).toThrow(NO_INACTIVE_OR_BASE_ERROR)
+    })
+
+    it("throws error when no active state is present", () => {
+        const state = {
+            inactive: { background: "#000000", color: "#CCCCCC" },
+        }
+
+        expect(() => toggleable({ state })).toThrow(NO_ACTIVE_ERROR)
+    })
+})

styles/src/element/toggle.ts πŸ”—

@@ -0,0 +1,47 @@
+import merge from "ts-deepmerge"
+import { DeepPartial } from "utility-types"
+
+type ToggleState = "inactive" | "active"
+
+type Toggleable<T> = Record<ToggleState, T>
+
+export const NO_INACTIVE_OR_BASE_ERROR =
+    "A toggleable object must have an inactive state, or a base property."
+export const NO_ACTIVE_ERROR = "A toggleable object must have an active state."
+
+interface ToggleableProps<T> {
+    base?: T
+    state: Partial<Record<ToggleState, DeepPartial<T>>>
+}
+
+/**
+ * Helper function for creating Toggleable objects.
+ * @template T The type of the object being toggled.
+ * @param props Object containing the base (inactive) state and state modifications to create the active state.
+ * @returns A Toggleable object containing both the inactive and active states.
+ * @example
+ * ```
+ * toggleable({
+ *   base: { background: "#000000", text: "#CCCCCC" },
+ *   state: { active: { text: "#CCCCCC" } },
+ * })
+ * ```
+ */
+export function toggleable<T extends object>(
+    props: ToggleableProps<T>
+): Toggleable<T> {
+    const { base, state } = props
+
+    if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR)
+    if (!state.active) throw new Error(NO_ACTIVE_ERROR)
+
+    const inactiveState = base
+        ? ((state.inactive ? merge(base, state.inactive) : base) as T)
+        : (state.inactive as T)
+
+    const toggleObj: Toggleable<T> = {
+        inactive: inactiveState,
+        active: merge(base ?? {}, state.active) as T,
+    }
+    return toggleObj
+}

styles/src/styleTree/app.ts πŸ”—

@@ -1,4 +1,3 @@
-import { text } from "./components"
 import contactFinder from "./contactFinder"
 import contactsPopover from "./contactsPopover"
 import commandPalette from "./commandPalette"
@@ -24,6 +23,7 @@ import feedback from "./feedback"
 import welcome from "./welcome"
 import copilot from "./copilot"
 import assistant from "./assistant"
+import { titlebar } from "./titlebar"
 
 export default function app(colorScheme: ColorScheme): Object {
     return {
@@ -37,6 +37,7 @@ export default function app(colorScheme: ColorScheme): Object {
         incomingCallNotification: incomingCallNotification(colorScheme),
         picker: picker(colorScheme),
         workspace: workspace(colorScheme),
+        titlebar: titlebar(colorScheme),
         copilot: copilot(colorScheme),
         welcome: welcome(colorScheme),
         contextMenu: contextMenu(colorScheme),

styles/src/styleTree/assistant.ts πŸ”—

@@ -1,6 +1,7 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { text, border, background, foreground } from "./components"
 import editor from "./editor"
+import { interactive } from "../element"
 
 export default function assistant(colorScheme: ColorScheme) {
     const layer = colorScheme.highest
@@ -9,50 +10,245 @@ export default function assistant(colorScheme: ColorScheme) {
             background: editor(colorScheme).background,
             padding: { left: 12 },
         },
-        header: {
-            border: border(layer, "default", { bottom: true, top: true }),
+        messageHeader: {
             margin: { bottom: 6, top: 6 },
             background: editor(colorScheme).background,
         },
+        hamburgerButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/hamburger_15.svg",
+                    dimensions: {
+                        width: 15,
+                        height: 15,
+                    },
+                },
+                container: {
+                    padding: { left: 12, right: 8.5 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        splitButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/split_message_15.svg",
+                    dimensions: {
+                        width: 15,
+                        height: 15,
+                    },
+                },
+                container: {
+                    padding: { left: 8.5, right: 8.5 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        quoteButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/quote_15.svg",
+                    dimensions: {
+                        width: 15,
+                        height: 15,
+                    },
+                },
+                container: {
+                    padding: { left: 8.5, right: 8.5 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        assistButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/assist_15.svg",
+                    dimensions: {
+                        width: 15,
+                        height: 15,
+                    },
+                },
+                container: {
+                    padding: { left: 8.5, right: 8.5 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        zoomInButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/maximize_8.svg",
+                    dimensions: {
+                        width: 12,
+                        height: 12,
+                    },
+                },
+                container: {
+                    padding: { left: 10, right: 10 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        zoomOutButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/minimize_8.svg",
+                    dimensions: {
+                        width: 12,
+                        height: 12,
+                    },
+                },
+                container: {
+                    padding: { left: 10, right: 10 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        plusButton: interactive({
+            base: {
+                icon: {
+                    color: foreground(layer, "variant"),
+                    asset: "icons/plus_12.svg",
+                    dimensions: {
+                        width: 12,
+                        height: 12,
+                    },
+                },
+                container: {
+                    padding: { left: 10, right: 10 },
+                },
+            },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+        title: {
+            ...text(layer, "sans", "default", { size: "sm" }),
+        },
+        savedConversation: {
+            container: interactive({
+                base: {
+                    background: background(layer, "on"),
+                    padding: { top: 4, bottom: 4 },
+                },
+                state: {
+                    hovered: {
+                        background: background(layer, "on", "hovered"),
+                    },
+                },
+            }),
+            savedAt: {
+                margin: { left: 8 },
+                ...text(layer, "sans", "default", { size: "xs" }),
+            },
+            title: {
+                margin: { left: 16 },
+                ...text(layer, "sans", "default", {
+                    size: "sm",
+                    weight: "bold",
+                }),
+            },
+        },
         userSender: {
-            ...text(layer, "sans", "default", { size: "sm", weight: "bold" }),
+            default: {
+                ...text(layer, "sans", "default", {
+                    size: "sm",
+                    weight: "bold",
+                }),
+            },
         },
         assistantSender: {
-            ...text(layer, "sans", "accent", { size: "sm", weight: "bold" }),
+            default: {
+                ...text(layer, "sans", "accent", {
+                    size: "sm",
+                    weight: "bold",
+                }),
+            },
         },
         systemSender: {
-            ...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
+            default: {
+                ...text(layer, "sans", "variant", {
+                    size: "sm",
+                    weight: "bold",
+                }),
+            },
         },
         sentAt: {
             margin: { top: 2, left: 8 },
             ...text(layer, "sans", "default", { size: "2xs" }),
         },
-        modelInfoContainer: {
-            margin: { right: 16, top: 4 },
-        },
-        model: {
-            background: background(layer, "on"),
-            border: border(layer, "on", { overlay: true }),
-            padding: 4,
-            cornerRadius: 4,
-            ...text(layer, "sans", "default", { size: "xs" }),
-            hover: {
-                background: background(layer, "on", "hovered"),
+        model: interactive({
+            base: {
+                background: background(layer, "on"),
+                margin: { left: 12, right: 12, top: 12 },
+                padding: 4,
+                cornerRadius: 4,
+                ...text(layer, "sans", "default", { size: "xs" }),
             },
-        },
+            state: {
+                hovered: {
+                    background: background(layer, "on", "hovered"),
+                    border: border(layer, "on", { overlay: true }),
+                },
+            },
+        }),
         remainingTokens: {
             background: background(layer, "on"),
-            border: border(layer, "on", { overlay: true }),
+            margin: { top: 12, right: 24 },
             padding: 4,
-            margin: { left: 4 },
             cornerRadius: 4,
             ...text(layer, "sans", "positive", { size: "xs" }),
         },
         noRemainingTokens: {
             background: background(layer, "on"),
-            border: border(layer, "on", { overlay: true }),
+            margin: { top: 12, right: 24 },
             padding: 4,
-            margin: { left: 4 },
             cornerRadius: 4,
             ...text(layer, "sans", "negative", { size: "xs" }),
         },

styles/src/styleTree/commandPalette.ts πŸ”—

@@ -1,12 +1,13 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { withOpacity } from "../theme/color"
 import { text, background } from "./components"
+import { toggleable } from "../element"
 
 export default function commandPalette(colorScheme: ColorScheme) {
     let layer = colorScheme.highest
-    return {
-        keystrokeSpacing: 8,
-        key: {
+
+    const key = toggleable({
+        base: {
             text: text(layer, "mono", "variant", "default", { size: "xs" }),
             cornerRadius: 2,
             background: background(layer, "on"),
@@ -21,10 +22,21 @@ export default function commandPalette(colorScheme: ColorScheme) {
                 bottom: 1,
                 left: 2,
             },
+        },
+        state: {
             active: {
                 text: text(layer, "mono", "on", "default", { size: "xs" }),
                 background: withOpacity(background(layer, "on"), 0.2),
             },
         },
+    })
+
+    return {
+        keystrokeSpacing: 8,
+        // TODO: This should be a Toggle<ContainedText> on the rust side so we don't have to do this
+        key: {
+            inactive: { ...key.inactive },
+            active: key.active,
+        },
     }
 }

styles/src/styleTree/components.ts πŸ”—

@@ -85,7 +85,15 @@ export function foreground(
     return getStyle(layer, styleSetOrStyles, style).foreground
 }
 
-interface Text {
+interface Text extends Object {
+    family: keyof typeof fontFamilies
+    color: string
+    size: number
+    weight?: FontWeight
+    underline?: boolean
+}
+
+export interface TextStyle extends Object {
     family: keyof typeof fontFamilies
     color: string
     size: number

styles/src/styleTree/contactList.ts πŸ”—

@@ -1,6 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, borderColor, foreground, text } from "./components"
-
+import { interactive, toggleable } from "../element"
 export default function contactsPanel(colorScheme: ColorScheme) {
     const nameMargin = 8
     const sidePadding = 12
@@ -71,47 +71,85 @@ export default function contactsPanel(colorScheme: ColorScheme) {
         },
         rowHeight: 28,
         sectionIconSize: 8,
-        headerRow: {
-            ...text(layer, "mono", { size: "sm" }),
-            margin: { top: 14 },
-            padding: {
-                left: sidePadding,
-                right: sidePadding,
-            },
-            active: {
-                ...text(layer, "mono", "active", { size: "sm" }),
-                background: background(layer, "active"),
-            },
-        },
-        leaveCall: {
-            background: background(layer),
-            border: border(layer),
-            cornerRadius: 6,
-            margin: {
-                top: 1,
-            },
-            padding: {
-                top: 1,
-                bottom: 1,
-                left: 7,
-                right: 7,
-            },
-            ...text(layer, "sans", "variant", { size: "xs" }),
-            hover: {
-                ...text(layer, "sans", "hovered", { size: "xs" }),
-                background: background(layer, "hovered"),
-                border: border(layer, "hovered"),
-            },
-        },
+        headerRow: toggleable({
+            base: interactive({
+                base: {
+                    ...text(layer, "mono", { size: "sm" }),
+                    margin: { top: 14 },
+                    padding: {
+                        left: sidePadding,
+                        right: sidePadding,
+                    },
+                    background: background(layer, "default"), // posiewic: breaking change
+                },
+                state: {
+                    hovered: {
+                        background: background(layer, "hovered"),
+                    },
+                    clicked: {
+                        background: background(layer, "pressed"),
+                    },
+                }, // hack, we want headerRow to be interactive for whatever reason. It probably shouldn't be interactive in the first place.
+            }),
+            state: {
+                active: {
+                    default: {
+                        ...text(layer, "mono", "active", { size: "sm" }),
+                        background: background(layer, "active"),
+                    },
+                    hovered: {
+                        background: background(layer, "hovered"),
+                    },
+                    clicked: {
+                        background: background(layer, "pressed"),
+                    },
+                },
+            },
+        }),
+        leaveCall: interactive({
+            base: {
+                background: background(layer),
+                border: border(layer),
+                cornerRadius: 6,
+                margin: {
+                    top: 1,
+                },
+                padding: {
+                    top: 1,
+                    bottom: 1,
+                    left: 7,
+                    right: 7,
+                },
+                ...text(layer, "sans", "variant", { size: "xs" }),
+            },
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "hovered", { size: "xs" }),
+                    background: background(layer, "hovered"),
+                    border: border(layer, "hovered"),
+                },
+            },
+        }),
         contactRow: {
-            padding: {
-                left: sidePadding,
-                right: sidePadding,
+            inactive: {
+                default: {
+                    padding: {
+                        left: sidePadding,
+                        right: sidePadding,
+                    },
+                },
             },
             active: {
-                background: background(layer, "active"),
+                default: {
+                    background: background(layer, "active"),
+                    padding: {
+                        left: sidePadding,
+                        right: sidePadding,
+                    },
+                },
             },
         },
+
         contactAvatar: {
             cornerRadius: 10,
             width: 18,
@@ -135,12 +173,14 @@ export default function contactsPanel(colorScheme: ColorScheme) {
             },
         },
         contactButtonSpacing: nameMargin,
-        contactButton: {
-            ...contactButton,
-            hover: {
-                background: background(layer, "hovered"),
-            },
-        },
+        contactButton: interactive({
+            base: { ...contactButton },
+            state: {
+                hovered: {
+                    background: background(layer, "hovered"),
+                },
+            },
+        }),
         disabledButton: {
             ...contactButton,
             background: background(layer, "on"),
@@ -149,34 +189,52 @@ export default function contactsPanel(colorScheme: ColorScheme) {
         callingIndicator: {
             ...text(layer, "mono", "variant", { size: "xs" }),
         },
-        treeBranch: {
-            color: borderColor(layer),
-            width: 1,
-            hover: {
-                color: borderColor(layer),
-            },
-            active: {
-                color: borderColor(layer),
-            },
-        },
-        projectRow: {
-            ...projectRow,
-            background: background(layer),
-            icon: {
-                margin: { left: nameMargin },
-                color: foreground(layer, "variant"),
-                width: 12,
-            },
-            name: {
-                ...projectRow.name,
-                ...text(layer, "mono", { size: "sm" }),
-            },
-            hover: {
-                background: background(layer, "hovered"),
-            },
-            active: {
-                background: background(layer, "active"),
+        treeBranch: toggleable({
+            base: interactive({
+                base: {
+                    color: borderColor(layer),
+                    width: 1,
+                },
+                state: {
+                    hovered: {
+                        color: borderColor(layer),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        color: borderColor(layer),
+                    },
+                },
+            },
+        }),
+        projectRow: toggleable({
+            base: interactive({
+                base: {
+                    ...projectRow,
+                    background: background(layer),
+                    icon: {
+                        margin: { left: nameMargin },
+                        color: foreground(layer, "variant"),
+                        width: 12,
+                    },
+                    name: {
+                        ...projectRow.name,
+                        ...text(layer, "mono", { size: "sm" }),
+                    },
+                },
+                state: {
+                    hovered: {
+                        background: background(layer, "hovered"),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: { background: background(layer, "active") },
+                },
             },
-        },
+        }),
     }
 }

styles/src/styleTree/contactNotification.ts πŸ”—

@@ -1,6 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, foreground, text } from "./components"
-
+import { interactive } from "../element"
 const avatarSize = 12
 const headerPadding = 8
 
@@ -21,24 +21,32 @@ export default function contactNotification(colorScheme: ColorScheme): Object {
             ...text(layer, "sans", { size: "xs" }),
             margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
         },
-        button: {
-            ...text(layer, "sans", "on", { size: "xs" }),
-            background: background(layer, "on"),
-            padding: 4,
-            cornerRadius: 6,
-            margin: { left: 6 },
-            hover: {
-                background: background(layer, "on", "hovered"),
+        button: interactive({
+            base: {
+                ...text(layer, "sans", "on", { size: "xs" }),
+                background: background(layer, "on"),
+                padding: 4,
+                cornerRadius: 6,
+                margin: { left: 6 },
             },
-        },
+
+            state: {
+                hovered: {
+                    background: background(layer, "on", "hovered"),
+                },
+            },
+        }),
+
         dismissButton: {
-            color: foreground(layer, "variant"),
-            iconWidth: 8,
-            iconHeight: 8,
-            buttonWidth: 8,
-            buttonHeight: 8,
-            hover: {
-                color: foreground(layer, "hovered"),
+            default: {
+                color: foreground(layer, "variant"),
+                iconWidth: 8,
+                iconHeight: 8,
+                buttonWidth: 8,
+                buttonHeight: 8,
+                hover: {
+                    color: foreground(layer, "hovered"),
+                },
             },
         },
     }

styles/src/styleTree/contextMenu.ts πŸ”—

@@ -1,5 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, borderColor, text } from "./components"
+import { interactive, toggleable } from "../element"
 
 export default function contextMenu(colorScheme: ColorScheme) {
     let layer = colorScheme.middle
@@ -10,37 +11,54 @@ export default function contextMenu(colorScheme: ColorScheme) {
         shadow: colorScheme.popoverShadow,
         border: border(layer),
         keystrokeMargin: 30,
-        item: {
-            iconSpacing: 8,
-            iconWidth: 14,
-            padding: { left: 6, right: 6, top: 2, bottom: 2 },
-            cornerRadius: 6,
-            label: text(layer, "sans", { size: "sm" }),
-            keystroke: {
-                ...text(layer, "sans", "variant", {
-                    size: "sm",
-                    weight: "bold",
-                }),
-                padding: { left: 3, right: 3 },
-            },
-            hover: {
-                background: background(layer, "hovered"),
-                label: text(layer, "sans", "hovered", { size: "sm" }),
-                keystroke: {
-                    ...text(layer, "sans", "hovered", {
-                        size: "sm",
-                        weight: "bold",
-                    }),
-                    padding: { left: 3, right: 3 },
+        item: toggleable({
+            base: interactive({
+                base: {
+                    iconSpacing: 8,
+                    iconWidth: 14,
+                    padding: { left: 6, right: 6, top: 2, bottom: 2 },
+                    cornerRadius: 6,
+                    label: text(layer, "sans", { size: "sm" }),
+                    keystroke: {
+                        ...text(layer, "sans", "variant", {
+                            size: "sm",
+                            weight: "bold",
+                        }),
+                        padding: { left: 3, right: 3 },
+                    },
+                },
+                state: {
+                    hovered: {
+                        background: background(layer, "hovered"),
+                        label: text(layer, "sans", "hovered", { size: "sm" }),
+                        keystroke: {
+                            ...text(layer, "sans", "hovered", {
+                                size: "sm",
+                                weight: "bold",
+                            }),
+                            padding: { left: 3, right: 3 },
+                        },
+                    },
+                    clicked: {
+                        background: background(layer, "pressed"),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        background: background(layer, "active"),
+                    },
+                    hovered: {
+                        background: background(layer, "hovered"),
+                    },
+                    clicked: {
+                        background: background(layer, "pressed"),
+                    },
                 },
             },
-            active: {
-                background: background(layer, "active"),
-            },
-            activeHover: {
-                background: background(layer, "active"),
-            },
-        },
+        }),
+
         separator: {
             background: borderColor(layer),
             margin: { top: 2, bottom: 2 },

styles/src/styleTree/copilot.ts πŸ”—

@@ -1,60 +1,69 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, foreground, svg, text } from "./components"
-
+import { interactive } from "../element"
 export default function copilot(colorScheme: ColorScheme) {
     let layer = colorScheme.middle
 
     let content_width = 264
 
-    let ctaButton = {
+    let ctaButton =
         // Copied from welcome screen. FIXME: Move this into a ZDS component
-        background: background(layer),
-        border: border(layer, "default"),
-        cornerRadius: 4,
-        margin: {
-            top: 4,
-            bottom: 4,
-            left: 8,
-            right: 8,
-        },
-        padding: {
-            top: 3,
-            bottom: 3,
-            left: 7,
-            right: 7,
-        },
-        ...text(layer, "sans", "default", { size: "sm" }),
-        hover: {
-            ...text(layer, "sans", "default", { size: "sm" }),
-            background: background(layer, "hovered"),
-            border: border(layer, "active"),
-        },
-    }
+        interactive({
+            base: {
+                background: background(layer),
+                border: border(layer, "default"),
+                cornerRadius: 4,
+                margin: {
+                    top: 4,
+                    bottom: 4,
+                    left: 8,
+                    right: 8,
+                },
+                padding: {
+                    top: 3,
+                    bottom: 3,
+                    left: 7,
+                    right: 7,
+                },
+                ...text(layer, "sans", "default", { size: "sm" }),
+            },
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "default", { size: "sm" }),
+                    background: background(layer, "hovered"),
+                    border: border(layer, "active"),
+                },
+            },
+        })
 
     return {
-        outLinkIcon: {
-            icon: svg(
-                foreground(layer, "variant"),
-                "icons/link_out_12.svg",
-                12,
-                12
-            ),
-            container: {
-                cornerRadius: 6,
-                padding: { left: 6 },
-            },
-            hover: {
+        outLinkIcon: interactive({
+            base: {
                 icon: svg(
-                    foreground(layer, "hovered"),
+                    foreground(layer, "variant"),
                     "icons/link_out_12.svg",
                     12,
                     12
                 ),
+                container: {
+                    cornerRadius: 6,
+                    padding: { left: 6 },
+                },
             },
-        },
+            state: {
+                hovered: {
+                    icon: {
+                        color: foreground(layer, "hovered"),
+                    },
+                },
+            },
+        }),
+
         modal: {
             titleText: {
-                ...text(layer, "sans", { size: "xs", weight: "bold" }),
+                default: {
+                    ...text(layer, "sans", { size: "xs", weight: "bold" }),
+                },
             },
             titlebar: {
                 background: background(colorScheme.lowest),
@@ -75,42 +84,46 @@ export default function copilot(colorScheme: ColorScheme) {
                     bottom: 8,
                 },
             },
-            closeIcon: {
-                icon: svg(
-                    foreground(layer, "variant"),
-                    "icons/x_mark_8.svg",
-                    8,
-                    8
-                ),
-                container: {
-                    cornerRadius: 2,
-                    padding: {
-                        top: 4,
-                        bottom: 4,
-                        left: 4,
-                        right: 4,
-                    },
-                    margin: {
-                        right: 0,
-                    },
-                },
-                hover: {
+            closeIcon: interactive({
+                base: {
                     icon: svg(
-                        foreground(layer, "on"),
+                        foreground(layer, "variant"),
                         "icons/x_mark_8.svg",
                         8,
                         8
                     ),
+                    container: {
+                        cornerRadius: 2,
+                        padding: {
+                            top: 4,
+                            bottom: 4,
+                            left: 4,
+                            right: 4,
+                        },
+                        margin: {
+                            right: 0,
+                        },
+                    },
                 },
-                clicked: {
-                    icon: svg(
-                        foreground(layer, "base"),
-                        "icons/x_mark_8.svg",
-                        8,
-                        8
-                    ),
+                state: {
+                    hovered: {
+                        icon: svg(
+                            foreground(layer, "on"),
+                            "icons/x_mark_8.svg",
+                            8,
+                            8
+                        ),
+                    },
+                    clicked: {
+                        icon: svg(
+                            foreground(layer, "base"),
+                            "icons/x_mark_8.svg",
+                            8,
+                            8
+                        ),
+                    },
                 },
-            },
+            }),
             dimensions: {
                 width: 280,
                 height: 280,
@@ -185,28 +198,32 @@ export default function copilot(colorScheme: ColorScheme) {
                         },
                     },
                     right: (content_width * 1) / 3,
-                    rightContainer: {
-                        border: border(colorScheme.lowest, "inverted", {
-                            bottom: false,
-                            right: false,
-                            top: false,
-                            left: true,
-                        }),
-                        padding: {
-                            top: 3,
-                            bottom: 5,
-                            left: 8,
-                            right: 0,
-                        },
-                        hover: {
-                            border: border(layer, "active", {
+                    rightContainer: interactive({
+                        base: {
+                            border: border(colorScheme.lowest, "inverted", {
                                 bottom: false,
                                 right: false,
                                 top: false,
                                 left: true,
                             }),
+                            padding: {
+                                top: 3,
+                                bottom: 5,
+                                left: 8,
+                                right: 0,
+                            },
                         },
-                    },
+                        state: {
+                            hovered: {
+                                border: border(layer, "active", {
+                                    bottom: false,
+                                    right: false,
+                                    top: false,
+                                    left: true,
+                                }),
+                            },
+                        },
+                    }),
                 },
             },
 

styles/src/styleTree/editor.ts πŸ”—

@@ -4,6 +4,7 @@ import { background, border, borderColor, foreground, text } from "./components"
 import hoverPopover from "./hoverPopover"
 
 import { buildSyntax } from "../theme/syntax"
+import { interactive, toggleable } from "../element"
 
 export default function editor(colorScheme: ColorScheme) {
     const { isLight } = colorScheme
@@ -48,46 +49,76 @@ export default function editor(colorScheme: ColorScheme) {
         // Inline autocomplete suggestions, Co-pilot suggestions, etc.
         suggestion: syntax.predictive,
         codeActions: {
-            indicator: {
-                color: foreground(layer, "variant"),
-
-                clicked: {
-                    color: foreground(layer, "base"),
-                },
-                hover: {
-                    color: foreground(layer, "on"),
-                },
-                active: {
-                    color: foreground(layer, "on"),
+            indicator: toggleable({
+                base: interactive({
+                    base: {
+                        color: foreground(layer, "variant"),
+                    },
+                    state: {
+                        hovered: {
+                            color: foreground(layer, "variant", "hovered"),
+                        },
+                        clicked: {
+                            color: foreground(layer, "variant", "pressed"),
+                        },
+                    },
+                }),
+                state: {
+                    active: {
+                        default: {
+                            color: foreground(layer, "accent"),
+                        },
+                        hovered: {
+                            color: foreground(layer, "accent", "hovered"),
+                        },
+                        clicked: {
+                            color: foreground(layer, "accent", "pressed"),
+                        },
+                    },
                 },
-            },
+            }),
+
             verticalScale: 0.55,
         },
         folds: {
             iconMarginScale: 2.5,
             foldedIcon: "icons/chevron_right_8.svg",
             foldableIcon: "icons/chevron_down_8.svg",
-            indicator: {
-                color: foreground(layer, "variant"),
-
-                clicked: {
-                    color: foreground(layer, "base"),
-                },
-                hover: {
-                    color: foreground(layer, "on"),
-                },
-                active: {
-                    color: foreground(layer, "on"),
+            indicator: toggleable({
+                base: interactive({
+                    base: {
+                        color: foreground(layer, "variant"),
+                    },
+                    state: {
+                        hovered: {
+                            color: foreground(layer, "on"),
+                        },
+                        clicked: {
+                            color: foreground(layer, "base"),
+                        },
+                    },
+                }),
+                state: {
+                    active: {
+                        default: {
+                            color: foreground(layer, "default"),
+                        },
+                        hovered: {
+                            color: foreground(layer, "variant"),
+                        },
+                    },
                 },
-            },
+            }),
             ellipses: {
                 textColor: colorScheme.ramps.neutral(0.71).hex(),
                 cornerRadiusFactor: 0.15,
                 background: {
                     // Copied from hover_popover highlight
-                    color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
+                    default: {
+                        color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
+                    },
 
-                    hover: {
+                    hovered: {
                         color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(),
                     },
 
@@ -223,21 +254,26 @@ export default function editor(colorScheme: ColorScheme) {
             color: syntax.linkUri.color,
             underline: syntax.linkUri.underline,
         },
-        jumpIcon: {
-            color: foreground(layer, "on"),
-            iconWidth: 20,
-            buttonWidth: 20,
-            cornerRadius: 6,
-            padding: {
-                top: 6,
-                bottom: 6,
-                left: 6,
-                right: 6,
+        jumpIcon: interactive({
+            base: {
+                color: foreground(layer, "on"),
+                iconWidth: 20,
+                buttonWidth: 20,
+                cornerRadius: 6,
+                padding: {
+                    top: 6,
+                    bottom: 6,
+                    left: 6,
+                    right: 6,
+                },
             },
-            hover: {
-                background: background(layer, "on", "hovered"),
+            state: {
+                hovered: {
+                    background: background(layer, "on", "hovered"),
+                },
             },
-        },
+        }),
+
         scrollbar: {
             width: 12,
             minHeightFactor: 1.0,

styles/src/styleTree/feedback.ts πŸ”—

@@ -1,35 +1,40 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, text } from "./components"
+import { interactive } from "../element"
 
 export default function feedback(colorScheme: ColorScheme) {
     let layer = colorScheme.highest
 
     return {
-        submit_button: {
-            ...text(layer, "mono", "on"),
-            background: background(layer, "on"),
-            cornerRadius: 6,
-            border: border(layer, "on"),
-            margin: {
-                right: 4,
+        submit_button: interactive({
+            base: {
+                ...text(layer, "mono", "on"),
+                background: background(layer, "on"),
+                cornerRadius: 6,
+                border: border(layer, "on"),
+                margin: {
+                    right: 4,
+                },
+                padding: {
+                    bottom: 2,
+                    left: 10,
+                    right: 10,
+                    top: 2,
+                },
             },
-            padding: {
-                bottom: 2,
-                left: 10,
-                right: 10,
-                top: 2,
+            state: {
+                clicked: {
+                    ...text(layer, "mono", "on", "pressed"),
+                    background: background(layer, "on", "pressed"),
+                    border: border(layer, "on", "pressed"),
+                },
+                hovered: {
+                    ...text(layer, "mono", "on", "hovered"),
+                    background: background(layer, "on", "hovered"),
+                    border: border(layer, "on", "hovered"),
+                },
             },
-            clicked: {
-                ...text(layer, "mono", "on", "pressed"),
-                background: background(layer, "on", "pressed"),
-                border: border(layer, "on", "pressed"),
-            },
-            hover: {
-                ...text(layer, "mono", "on", "hovered"),
-                background: background(layer, "on", "hovered"),
-                border: border(layer, "on", "hovered"),
-            },
-        },
+        }),
         button_margin: 8,
         info_text_default: text(layer, "sans", "default", { size: "xs" }),
         link_text_default: text(layer, "sans", "default", {

styles/src/styleTree/picker.ts πŸ”—

@@ -1,6 +1,7 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { withOpacity } from "../theme/color"
 import { background, border, text } from "./components"
+import { interactive, toggleable } from "../element"
 
 export default function picker(colorScheme: ColorScheme): any {
     let layer = colorScheme.lowest
@@ -38,35 +39,65 @@ export default function picker(colorScheme: ColorScheme): any {
             ...container,
             padding: {},
         },
-        item: {
-            padding: {
-                bottom: 4,
-                left: 12,
-                right: 12,
-                top: 4,
-            },
-            margin: {
-                top: 1,
-                left: 4,
-                right: 4,
-            },
-            cornerRadius: 8,
-            text: text(layer, "sans", "variant"),
-            highlightText: text(layer, "sans", "accent", { weight: "bold" }),
-            active: {
-                background: withOpacity(
-                    background(layer, "base", "active"),
-                    0.5
-                ),
-                text: text(layer, "sans", "base", "active"),
-                highlightText: text(layer, "sans", "accent", {
-                    weight: "bold",
-                }),
+        item: toggleable({
+            base: interactive({
+                base: {
+                    padding: {
+                        bottom: 4,
+                        left: 12,
+                        right: 12,
+                        top: 4,
+                    },
+                    margin: {
+                        top: 1,
+                        left: 4,
+                        right: 4,
+                    },
+                    cornerRadius: 8,
+                    text: text(layer, "sans", "variant"),
+                    highlightText: text(layer, "sans", "accent", {
+                        weight: "bold",
+                    }),
+                },
+                state: {
+                    hovered: {
+                        background: withOpacity(
+                            background(layer, "hovered"),
+                            0.5
+                        ),
+                    },
+                    clicked: {
+                        background: withOpacity(
+                            background(layer, "pressed"),
+                            0.5
+                        ),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        background: withOpacity(
+                            background(layer, "base", "active"),
+                            0.5
+                        ),
+                    },
+                    hovered: {
+                        background: withOpacity(
+                            background(layer, "hovered"),
+                            0.5
+                        ),
+                    },
+                    clicked: {
+                        background: withOpacity(
+                            background(layer, "pressed"),
+                            0.5
+                        ),
+                    },
+                },
             },
-            hover: {
-                background: withOpacity(background(layer, "hovered"), 0.5),
-            },
-        },
+        }),
+
         inputEditor,
         emptyInputEditor,
         noMatches: {

styles/src/styleTree/projectPanel.ts πŸ”—

@@ -1,103 +1,184 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { withOpacity } from "../theme/color"
-import { background, border, foreground, text } from "./components"
-
+import {
+    Border,
+    TextStyle,
+    background,
+    border,
+    foreground,
+    text,
+} from "./components"
+import { interactive, toggleable } from "../element"
+import merge from "ts-deepmerge"
 export default function projectPanel(colorScheme: ColorScheme) {
     const { isLight } = colorScheme
 
     let layer = colorScheme.middle
 
-    let baseEntry = {
-        height: 22,
-        iconColor: foreground(layer, "variant"),
-        iconSize: 7,
-        iconSpacing: 5,
+    type EntryStateProps = {
+        background?: string
+        border?: Border
+        text?: TextStyle
+        iconColor?: string
     }
 
-    let status = {
-        git: {
-            modified: isLight
-                ? colorScheme.ramps.yellow(0.6).hex()
-                : colorScheme.ramps.yellow(0.5).hex(),
-            inserted: isLight
-                ? colorScheme.ramps.green(0.45).hex()
-                : colorScheme.ramps.green(0.5).hex(),
-            conflict: isLight
-                ? colorScheme.ramps.red(0.6).hex()
-                : colorScheme.ramps.red(0.5).hex(),
-        },
+    type EntryState = {
+        default: EntryStateProps
+        hovered?: EntryStateProps
+        clicked?: EntryStateProps
     }
 
-    let entry = {
-        ...baseEntry,
-        text: text(layer, "mono", "variant", { size: "sm" }),
-        hover: {
-            background: background(layer, "variant", "hovered"),
-        },
-        active: {
-            background: colorScheme.isLight
-                ? withOpacity(background(layer, "active"), 0.5)
-                : background(layer, "active"),
-            text: text(layer, "mono", "active", { size: "sm" }),
-        },
-        activeHover: {
-            background: background(layer, "active"),
-            text: text(layer, "mono", "active", { size: "sm" }),
-        },
-        status,
-    }
+    const entry = (unselected?: EntryState, selected?: EntryState) => {
+        const git_status = {
+            git: {
+                modified: isLight
+                    ? colorScheme.ramps.yellow(0.6).hex()
+                    : colorScheme.ramps.yellow(0.5).hex(),
+                inserted: isLight
+                    ? colorScheme.ramps.green(0.45).hex()
+                    : colorScheme.ramps.green(0.5).hex(),
+                conflict: isLight
+                    ? colorScheme.ramps.red(0.6).hex()
+                    : colorScheme.ramps.red(0.5).hex(),
+            },
+        }
 
-    return {
-        openProjectButton: {
+        const base_properties = {
+            height: 22,
             background: background(layer),
-            border: border(layer, "active"),
-            cornerRadius: 4,
-            margin: {
-                top: 16,
-                left: 16,
-                right: 16,
+            iconColor: foreground(layer, "variant"),
+            iconSize: 7,
+            iconSpacing: 5,
+            text: text(layer, "mono", "variant", { size: "sm" }),
+            status: {
+                ...git_status,
             },
-            padding: {
-                top: 3,
-                bottom: 3,
-                left: 7,
-                right: 7,
+        }
+
+        const selectedStyle: EntryState | undefined = selected
+            ? selected
+            : unselected
+
+        const unselected_default_style = merge(
+            base_properties,
+            unselected?.default ?? {},
+            {}
+        )
+        const unselected_hovered_style = merge(
+            base_properties,
+            unselected?.hovered ?? {},
+            { background: background(layer, "variant", "hovered") }
+        )
+        const unselected_clicked_style = merge(
+            base_properties,
+            unselected?.clicked ?? {},
+            { background: background(layer, "variant", "pressed") }
+        )
+        const selected_default_style = merge(
+            base_properties,
+            selectedStyle?.default ?? {},
+            { background: background(layer) }
+        )
+        const selected_hovered_style = merge(
+            base_properties,
+            selectedStyle?.hovered ?? {},
+            { background: background(layer, "variant", "hovered") }
+        )
+        const selected_clicked_style = merge(
+            base_properties,
+            selectedStyle?.clicked ?? {},
+            { background: background(layer, "variant", "pressed") }
+        )
+
+        return toggleable({
+            state: {
+                inactive: interactive({
+                    state: {
+                        default: unselected_default_style,
+                        hovered: unselected_hovered_style,
+                        clicked: unselected_clicked_style,
+                    },
+                }),
+                active: interactive({
+                    state: {
+                        default: selected_default_style,
+                        hovered: selected_hovered_style,
+                        clicked: selected_clicked_style,
+                    },
+                }),
             },
-            ...text(layer, "sans", "default", { size: "sm" }),
-            hover: {
-                ...text(layer, "sans", "default", { size: "sm" }),
-                background: background(layer, "hovered"),
+        })
+    }
+
+    const defaultEntry = entry()
+
+    return {
+        openProjectButton: interactive({
+            base: {
+                background: background(layer),
                 border: border(layer, "active"),
+                cornerRadius: 4,
+                margin: {
+                    top: 16,
+                    left: 16,
+                    right: 16,
+                },
+                padding: {
+                    top: 3,
+                    bottom: 3,
+                    left: 7,
+                    right: 7,
+                },
+                ...text(layer, "sans", "default", { size: "sm" }),
             },
-        },
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "default", { size: "sm" }),
+                    background: background(layer, "hovered"),
+                    border: border(layer, "active"),
+                },
+                clicked: {
+                    ...text(layer, "sans", "default", { size: "sm" }),
+                    background: background(layer, "pressed"),
+                    border: border(layer, "active"),
+                },
+            },
+        }),
         background: background(layer),
         padding: { left: 6, right: 6, top: 0, bottom: 6 },
         indentWidth: 12,
-        entry,
+        entry: defaultEntry,
         draggedEntry: {
-            ...baseEntry,
+            ...defaultEntry.inactive.default,
             text: text(layer, "mono", "on", { size: "sm" }),
             background: withOpacity(background(layer, "on"), 0.9),
             border: border(layer),
-            status,
         },
-        ignoredEntry: {
-            ...entry,
-            iconColor: foreground(layer, "disabled"),
-            text: text(layer, "mono", "disabled"),
-            active: {
-                ...entry.active,
-                iconColor: foreground(layer, "variant"),
+        ignoredEntry: entry(
+            {
+                default: {
+                    text: text(layer, "mono", "disabled"),
+                },
             },
-        },
-        cutEntry: {
-            ...entry,
-            text: text(layer, "mono", "disabled"),
-            active: {
-                background: background(layer, "active"),
-                text: text(layer, "mono", "disabled", { size: "sm" }),
+            {
+                default: {
+                    iconColor: foreground(layer, "variant"),
+                },
+            }
+        ),
+        cutEntry: entry(
+            {
+                default: {
+                    text: text(layer, "mono", "disabled"),
+                },
             },
-        },
+            {
+                default: {
+                    background: background(layer, "active"),
+                    text: text(layer, "mono", "disabled", { size: "sm" }),
+                },
+            }
+        ),
         filenameEditor: {
             background: background(layer, "on"),
             text: text(layer, "mono", "on", { size: "sm" }),

styles/src/styleTree/search.ts πŸ”—

@@ -1,6 +1,7 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { withOpacity } from "../theme/color"
 import { background, border, foreground, text } from "./components"
+import { interactive, toggleable } from "../element"
 
 export default function search(colorScheme: ColorScheme) {
     let layer = colorScheme.highest
@@ -35,36 +36,50 @@ export default function search(colorScheme: ColorScheme) {
     return {
         // TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive
         matchBackground: withOpacity(foreground(layer, "accent"), 0.4),
-        optionButton: {
-            ...text(layer, "mono", "on"),
-            background: background(layer, "on"),
-            cornerRadius: 6,
-            border: border(layer, "on"),
-            margin: {
-                right: 4,
-            },
-            padding: {
-                bottom: 2,
-                left: 10,
-                right: 10,
-                top: 2,
-            },
-            active: {
-                ...text(layer, "mono", "on", "inverted"),
-                background: background(layer, "on", "inverted"),
-                border: border(layer, "on", "inverted"),
-            },
-            clicked: {
-                ...text(layer, "mono", "on", "pressed"),
-                background: background(layer, "on", "pressed"),
-                border: border(layer, "on", "pressed"),
+        optionButton: toggleable({
+            base: interactive({
+                base: {
+                    ...text(layer, "mono", "on"),
+                    background: background(layer, "on"),
+                    cornerRadius: 6,
+                    border: border(layer, "on"),
+                    margin: {
+                        right: 4,
+                    },
+                    padding: {
+                        bottom: 2,
+                        left: 10,
+                        right: 10,
+                        top: 2,
+                    },
+                },
+                state: {
+                    hovered: {
+                        ...text(layer, "mono", "on", "hovered"),
+                        background: background(layer, "on", "hovered"),
+                        border: border(layer, "on", "hovered"),
+                    },
+                    clicked: {
+                        ...text(layer, "mono", "on", "pressed"),
+                        background: background(layer, "on", "pressed"),
+                        border: border(layer, "on", "pressed"),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        ...text(layer, "mono", "accent"),
+                    },
+                    hovered: {
+                        ...text(layer, "mono", "accent", "hovered"),
+                    },
+                    clicked: {
+                        ...text(layer, "mono", "accent", "pressed"),
+                    },
+                },
             },
-            hover: {
-                ...text(layer, "mono", "on", "hovered"),
-                background: background(layer, "on", "hovered"),
-                border: border(layer, "on", "hovered"),
-            },
-        },
+        }),
         editor,
         invalidEditor: {
             ...editor,
@@ -97,17 +112,24 @@ export default function search(colorScheme: ColorScheme) {
             ...text(layer, "mono", "on"),
             size: 18,
         },
-        dismissButton: {
-            color: foreground(layer, "variant"),
-            iconWidth: 12,
-            buttonWidth: 14,
-            padding: {
-                left: 10,
-                right: 10,
+        dismissButton: interactive({
+            base: {
+                color: foreground(layer, "variant"),
+                iconWidth: 12,
+                buttonWidth: 14,
+                padding: {
+                    left: 10,
+                    right: 10,
+                },
             },
-            hover: {
-                color: foreground(layer, "hovered"),
+            state: {
+                hovered: {
+                    color: foreground(layer, "hovered"),
+                },
+                clicked: {
+                    color: foreground(layer, "pressed"),
+                },
             },
-        },
+        }),
     }
 }

styles/src/styleTree/simpleMessageNotification.ts πŸ”—

@@ -1,5 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, foreground, text } from "./components"
+import { interactive } from "../element"
 
 const headerPadding = 8
 
@@ -12,33 +13,41 @@ export default function simpleMessageNotification(
             ...text(layer, "sans", { size: "xs" }),
             margin: { left: headerPadding, right: headerPadding },
         },
-        actionMessage: {
-            ...text(layer, "sans", { size: "xs" }),
-            border: border(layer, "active"),
-            cornerRadius: 4,
-            padding: {
-                top: 3,
-                bottom: 3,
-                left: 7,
-                right: 7,
-            },
-
-            margin: { left: headerPadding, top: 6, bottom: 6 },
-            hover: {
-                ...text(layer, "sans", "default", { size: "xs" }),
-                background: background(layer, "hovered"),
+        actionMessage: interactive({
+            base: {
+                ...text(layer, "sans", { size: "xs" }),
                 border: border(layer, "active"),
+                cornerRadius: 4,
+                padding: {
+                    top: 3,
+                    bottom: 3,
+                    left: 7,
+                    right: 7,
+                },
+
+                margin: { left: headerPadding, top: 6, bottom: 6 },
             },
-        },
-        dismissButton: {
-            color: foreground(layer),
-            iconWidth: 8,
-            iconHeight: 8,
-            buttonWidth: 8,
-            buttonHeight: 8,
-            hover: {
-                color: foreground(layer, "hovered"),
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "default", { size: "xs" }),
+                    background: background(layer, "hovered"),
+                    border: border(layer, "active"),
+                },
             },
-        },
+        }),
+        dismissButton: interactive({
+            base: {
+                color: foreground(layer),
+                iconWidth: 8,
+                iconHeight: 8,
+                buttonWidth: 8,
+                buttonHeight: 8,
+            },
+            state: {
+                hovered: {
+                    color: foreground(layer, "hovered"),
+                },
+            },
+        }),
     }
 }

styles/src/styleTree/statusBar.ts πŸ”—

@@ -1,6 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, foreground, text } from "./components"
-
+import { interactive, toggleable } from "../element"
 export default function statusBar(colorScheme: ColorScheme) {
     let layer = colorScheme.lowest
 
@@ -25,95 +25,123 @@ export default function statusBar(colorScheme: ColorScheme) {
         },
         border: border(layer, { top: true, overlay: true }),
         cursorPosition: text(layer, "sans", "variant"),
-        activeLanguage: {
-            padding: { left: 6, right: 6 },
-            ...text(layer, "sans", "variant"),
-            hover: {
-                ...text(layer, "sans", "on"),
+        activeLanguage: interactive({
+            base: {
+                padding: { left: 6, right: 6 },
+                ...text(layer, "sans", "variant"),
             },
-        },
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "on"),
+                },
+            },
+        }),
         autoUpdateProgressMessage: text(layer, "sans", "variant"),
         autoUpdateDoneMessage: text(layer, "sans", "variant"),
-        lspStatus: {
-            ...diagnosticStatusContainer,
-            iconSpacing: 4,
-            iconWidth: 14,
-            height: 18,
-            message: text(layer, "sans"),
-            iconColor: foreground(layer),
-            hover: {
+        lspStatus: interactive({
+            base: {
+                ...diagnosticStatusContainer,
+                iconSpacing: 4,
+                iconWidth: 14,
+                height: 18,
                 message: text(layer, "sans"),
                 iconColor: foreground(layer),
-                background: background(layer, "hovered"),
             },
-        },
-        diagnosticMessage: {
-            ...text(layer, "sans"),
-            hover: text(layer, "sans", "hovered"),
-        },
-        diagnosticSummary: {
-            height: 20,
-            iconWidth: 16,
-            iconSpacing: 2,
-            summarySpacing: 6,
-            text: text(layer, "sans", { size: "sm" }),
-            iconColorOk: foreground(layer, "variant"),
-            iconColorWarning: foreground(layer, "warning"),
-            iconColorError: foreground(layer, "negative"),
-            containerOk: {
-                cornerRadius: 6,
-                padding: { top: 3, bottom: 3, left: 7, right: 7 },
-            },
-            containerWarning: {
-                ...diagnosticStatusContainer,
-                background: background(layer, "warning"),
-                border: border(layer, "warning"),
+            state: {
+                hovered: {
+                    message: text(layer, "sans"),
+                    iconColor: foreground(layer),
+                    background: background(layer, "hovered"),
+                },
             },
-            containerError: {
-                ...diagnosticStatusContainer,
-                background: background(layer, "negative"),
-                border: border(layer, "negative"),
+        }),
+        diagnosticMessage: interactive({
+            base: {
+                ...text(layer, "sans"),
             },
-            hover: {
-                iconColorOk: foreground(layer, "on"),
+            state: { hovered: text(layer, "sans", "hovered") },
+        }),
+        diagnosticSummary: interactive({
+            base: {
+                height: 20,
+                iconWidth: 16,
+                iconSpacing: 2,
+                summarySpacing: 6,
+                text: text(layer, "sans", { size: "sm" }),
+                iconColorOk: foreground(layer, "variant"),
+                iconColorWarning: foreground(layer, "warning"),
+                iconColorError: foreground(layer, "negative"),
                 containerOk: {
                     cornerRadius: 6,
                     padding: { top: 3, bottom: 3, left: 7, right: 7 },
-                    background: background(layer, "on", "hovered"),
                 },
                 containerWarning: {
                     ...diagnosticStatusContainer,
-                    background: background(layer, "warning", "hovered"),
-                    border: border(layer, "warning", "hovered"),
+                    background: background(layer, "warning"),
+                    border: border(layer, "warning"),
                 },
                 containerError: {
                     ...diagnosticStatusContainer,
-                    background: background(layer, "negative", "hovered"),
-                    border: border(layer, "negative", "hovered"),
+                    background: background(layer, "negative"),
+                    border: border(layer, "negative"),
                 },
             },
-        },
+            state: {
+                hovered: {
+                    iconColorOk: foreground(layer, "on"),
+                    containerOk: {
+                        background: background(layer, "on", "hovered"),
+                    },
+                    containerWarning: {
+                        background: background(layer, "warning", "hovered"),
+                        border: border(layer, "warning", "hovered"),
+                    },
+                    containerError: {
+                        background: background(layer, "negative", "hovered"),
+                        border: border(layer, "negative", "hovered"),
+                    },
+                },
+            },
+        }),
         panelButtons: {
             groupLeft: {},
             groupBottom: {},
             groupRight: {},
-            button: {
-                ...statusContainer,
-                iconSize: 16,
-                iconColor: foreground(layer, "variant"),
-                label: {
-                    margin: { left: 6 },
-                    ...text(layer, "sans", { size: "sm" }),
-                },
-                hover: {
-                    iconColor: foreground(layer, "hovered"),
-                    background: background(layer, "variant"),
+            button: toggleable({
+                base: interactive({
+                    base: {
+                        ...statusContainer,
+                        iconSize: 16,
+                        iconColor: foreground(layer, "variant"),
+                        label: {
+                            margin: { left: 6 },
+                            ...text(layer, "sans", { size: "sm" }),
+                        },
+                    },
+                    state: {
+                        hovered: {
+                            iconColor: foreground(layer, "hovered"),
+                            background: background(layer, "variant"),
+                        },
+                    },
+                }),
+                state: {
+                    active: {
+                        default: {
+                            iconColor: foreground(layer, "active"),
+                            background: background(layer, "active"),
+                        },
+                        hovered: {
+                            iconColor: foreground(layer, "hovered"),
+                            background: background(layer, "hovered"),
+                        },
+                        clicked: {
+                            iconColor: foreground(layer, "pressed"),
+                            background: background(layer, "pressed"),
+                        },
+                    },
                 },
-                active: {
-                    iconColor: foreground(layer, "active"),
-                    background: background(layer, "active"),
-                },
-            },
+            }),
             badge: {
                 cornerRadius: 3,
                 padding: 2,

styles/src/styleTree/tabBar.ts πŸ”—

@@ -1,6 +1,7 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { withOpacity } from "../theme/color"
 import { text, border, background, foreground } from "./components"
+import { interactive, toggleable } from "../element"
 
 export default function tabBar(colorScheme: ColorScheme) {
     const height = 32
@@ -87,17 +88,36 @@ export default function tabBar(colorScheme: ColorScheme) {
             inactiveTab: inactivePaneInactiveTab,
         },
         draggedTab,
-        paneButton: {
-            color: foreground(layer, "variant"),
-            iconWidth: 12,
-            buttonWidth: activePaneActiveTab.height,
-            hover: {
-                color: foreground(layer, "hovered"),
+        paneButton: toggleable({
+            base: interactive({
+                base: {
+                    color: foreground(layer, "variant"),
+                    iconWidth: 12,
+                    buttonWidth: activePaneActiveTab.height,
+                },
+                state: {
+                    hovered: {
+                        color: foreground(layer, "hovered"),
+                    },
+                    clicked: {
+                        color: foreground(layer, "pressed"),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        color: foreground(layer, "accent"),
+                    },
+                    hovered: {
+                        color: foreground(layer, "hovered"),
+                    },
+                    clicked: {
+                        color: foreground(layer, "pressed"),
+                    },
+                },
             },
-            active: {
-                color: foreground(layer, "accent"),
-            },
-        },
+        }),
         paneButtonContainer: {
             background: tab.background,
             border: {

styles/src/styleTree/titlebar.ts πŸ”—

@@ -0,0 +1,266 @@
+import { ColorScheme } from "../common"
+import { icon_button, toggleable_icon_button } from "../component/icon_button"
+import { toggleable_text_button } from "../component/text_button"
+import { interactive, toggleable } from "../element"
+import { withOpacity } from "../theme/color"
+import { background, border, foreground, text } from "./components"
+
+const ITEM_SPACING = 8
+const TITLEBAR_HEIGHT = 32
+
+function build_spacing(
+    container_height: number,
+    element_height: number,
+    spacing: number
+) {
+    return {
+        group: spacing,
+        item: spacing / 2,
+        half_item: spacing / 4,
+        marginY: (container_height - element_height) / 2,
+        marginX: (container_height - element_height) / 2,
+    }
+}
+
+function call_controls(theme: ColorScheme) {
+    const button_height = 18
+
+    const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
+    const marginY = {
+        top: space.marginY,
+        bottom: space.marginY,
+    }
+
+    return {
+        toggle_microphone_button: toggleable_icon_button(theme, {
+            margin: {
+                ...marginY,
+                left: space.group,
+                right: space.half_item,
+            },
+            active_color: "negative",
+        }),
+
+        toggle_speakers_button: toggleable_icon_button(theme, {
+            margin: {
+                ...marginY,
+                left: space.half_item,
+                right: space.half_item,
+            },
+        }),
+
+        screen_share_button: toggleable_icon_button(theme, {
+            margin: {
+                ...marginY,
+                left: space.half_item,
+                right: space.group,
+            },
+            active_color: "accent",
+        }),
+
+        muted: foreground(theme.lowest, "negative"),
+        speaking: foreground(theme.lowest, "accent"),
+    }
+}
+
+/**
+ * Opens the User Menu when toggled
+ *
+ * When logged in shows the user's avatar and a chevron,
+ * When logged out only shows a chevron.
+ */
+function user_menu(theme: ColorScheme) {
+    const button_height = 18
+
+    const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
+
+    const build_button = ({ online }: { online: boolean }) => {
+        const button = toggleable({
+            base: interactive({
+                base: {
+                    cornerRadius: 6,
+                    height: button_height,
+                    width: online ? 37 : 24,
+                    padding: {
+                        top: 2,
+                        bottom: 2,
+                        left: 6,
+                        right: 6,
+                    },
+                    margin: {
+                        left: space.item,
+                        right: space.item,
+                    },
+                    ...text(theme.lowest, "sans", { size: "xs" }),
+                    background: background(theme.lowest),
+                },
+                state: {
+                    hovered: {
+                        ...text(theme.lowest, "sans", "hovered", {
+                            size: "xs",
+                        }),
+                        background: background(theme.lowest, "hovered"),
+                    },
+                    clicked: {
+                        ...text(theme.lowest, "sans", "pressed", {
+                            size: "xs",
+                        }),
+                        background: background(theme.lowest, "pressed"),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
+                        background: background(theme.middle),
+                    },
+                    hovered: {
+                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
+                        background: background(theme.middle, "hovered"),
+                    },
+                    clicked: {
+                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
+                        background: background(theme.middle, "pressed"),
+                    },
+                },
+            },
+        })
+
+        return {
+            user_menu: button,
+            avatar: {
+                icon_width: 16,
+                icon_height: 16,
+                corner_radius: 4,
+                outer_width: 16,
+                outer_corner_radius: 16,
+            },
+            icon: {
+                margin: {
+                    top: 2,
+                    left: online ? space.item : 0,
+                    right: space.group,
+                    bottom: 2,
+                },
+                width: 11,
+                height: 11,
+                color: foreground(theme.lowest),
+            },
+        }
+    }
+    return {
+        userMenuButtonOnline: build_button({ online: true }),
+        userMenuButtonOffline: build_button({ online: false }),
+    }
+}
+
+export function titlebar(theme: ColorScheme) {
+    const avatarWidth = 15
+    const avatarOuterWidth = avatarWidth + 4
+    const followerAvatarWidth = 14
+    const followerAvatarOuterWidth = followerAvatarWidth + 4
+
+    return {
+        item_spacing: ITEM_SPACING,
+        facePileSpacing: 2,
+        height: TITLEBAR_HEIGHT,
+        background: background(theme.lowest),
+        border: border(theme.lowest, { bottom: true }),
+        padding: {
+            left: 80,
+            right: 0,
+        },
+
+        // Project
+        title: text(theme.lowest, "sans", "variant"),
+        highlight_color: text(theme.lowest, "sans", "active").color,
+
+        // Collaborators
+        leaderAvatar: {
+            width: avatarWidth,
+            outerWidth: avatarOuterWidth,
+            cornerRadius: avatarWidth / 2,
+            outerCornerRadius: avatarOuterWidth / 2,
+        },
+        followerAvatar: {
+            width: followerAvatarWidth,
+            outerWidth: followerAvatarOuterWidth,
+            cornerRadius: followerAvatarWidth / 2,
+            outerCornerRadius: followerAvatarOuterWidth / 2,
+        },
+        inactiveAvatarGrayscale: true,
+        followerAvatarOverlap: 8,
+        leaderSelection: {
+            margin: {
+                top: 4,
+                bottom: 4,
+            },
+            padding: {
+                left: 2,
+                right: 2,
+                top: 2,
+                bottom: 2,
+            },
+            cornerRadius: 6,
+        },
+        avatarRibbon: {
+            height: 3,
+            width: 14,
+            // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
+        },
+
+        sign_in_button: toggleable_text_button(theme, {}),
+        offlineIcon: {
+            color: foreground(theme.lowest, "variant"),
+            width: 16,
+            margin: {
+                left: ITEM_SPACING,
+            },
+            padding: {
+                right: 4,
+            },
+        },
+
+        // When the collaboration server is out of date, show a warning
+        outdatedWarning: {
+            ...text(theme.lowest, "sans", "warning", { size: "xs" }),
+            background: withOpacity(background(theme.lowest, "warning"), 0.3),
+            border: border(theme.lowest, "warning"),
+            margin: {
+                left: ITEM_SPACING,
+            },
+            padding: {
+                left: 8,
+                right: 8,
+            },
+            cornerRadius: 6,
+        },
+
+        leave_call_button: icon_button(theme, {
+            margin: {
+                left: ITEM_SPACING / 2,
+                right: ITEM_SPACING,
+            },
+        }),
+
+        ...call_controls(theme),
+
+        toggle_contacts_button: toggleable_icon_button(theme, {
+            margin: {
+                left: ITEM_SPACING,
+            },
+        }),
+
+        // Jewel that notifies you that there are new contact requests
+        toggleContactsBadge: {
+            cornerRadius: 3,
+            padding: 2,
+            margin: { top: 3, left: 3 },
+            border: border(theme.lowest),
+            background: foreground(theme.lowest, "accent"),
+        },
+        shareButton: toggleable_text_button(theme, {}),
+        user_menu: user_menu(theme),
+    }
+}

styles/src/styleTree/toggle.ts πŸ”—

@@ -0,0 +1,47 @@
+import merge from "ts-deepmerge"
+
+type ToggleState = "inactive" | "active"
+
+type Toggleable<T> = Record<ToggleState, T>
+
+const NO_INACTIVE_OR_BASE_ERROR =
+    "A toggleable object must have an inactive state, or a base property."
+const NO_ACTIVE_ERROR = "A toggleable object must have an active state."
+
+interface ToggleableProps<T> {
+    base?: T
+    state: Partial<Record<ToggleState, T>>
+}
+
+/**
+ * Helper function for creating Toggleable objects.
+ * @template T The type of the object being toggled.
+ * @param props Object containing the base (inactive) state and state modifications to create the active state.
+ * @returns A Toggleable object containing both the inactive and active states.
+ * @example
+ * ```
+ * toggleable({
+ *   base: { background: "#000000", text: "#CCCCCC" },
+ *   state: { active: { text: "#CCCCCC" } },
+ * })
+ * ```
+ */
+export function toggleable<T extends object>(
+    props: ToggleableProps<T>
+): Toggleable<T> {
+    const { base, state } = props
+
+    if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR)
+    if (!state.active) throw new Error(NO_ACTIVE_ERROR)
+
+    const inactiveState = base
+        ? ((state.inactive ? merge(base, state.inactive) : base) as T)
+        : (state.inactive as T)
+
+    const toggleObj: Toggleable<T> = {
+        inactive: inactiveState,
+        active: merge(base ?? {}, state.active) as T,
+    }
+
+    return toggleObj
+}

styles/src/styleTree/toolbarDropdownMenu.ts πŸ”—

@@ -1,6 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { background, border, text } from "./components"
-
+import { interactive, toggleable } from "../element"
 export default function dropdownMenu(colorScheme: ColorScheme) {
     let layer = colorScheme.middle
 
@@ -9,38 +9,56 @@ export default function dropdownMenu(colorScheme: ColorScheme) {
         background: background(layer),
         border: border(layer),
         shadow: colorScheme.popoverShadow,
-        header: {
-            ...text(layer, "sans", { size: "sm" }),
-            secondaryText: text(layer, "sans", { size: "sm", color: "#aaaaaa" }),
-            secondaryTextSpacing: 10,
-            padding: { left: 8, right: 8, top: 2, bottom: 2 },
-            cornerRadius: 6,
-            background: background(layer, "on"),
-            border: border(layer, "on", { overlay: true }),
-            hover: {
-                background: background(layer, "hovered"),
-                ...text(layer, "sans", "hovered", { size: "sm" }),
-            }
-        },
+        header: interactive({
+            base: {
+                ...text(layer, "sans", { size: "sm" }),
+                secondaryText: text(layer, "sans", {
+                    size: "sm",
+                    color: "#aaaaaa",
+                }),
+                secondaryTextSpacing: 10,
+                padding: { left: 8, right: 8, top: 2, bottom: 2 },
+                cornerRadius: 6,
+                background: background(layer, "on"),
+            },
+            state: {
+                hovered: {
+                    background: background(layer, "hovered"),
+                },
+                clicked: {
+                    background: background(layer, "pressed"),
+                },
+            },
+        }),
         sectionHeader: {
             ...text(layer, "sans", { size: "sm" }),
             padding: { left: 8, right: 8, top: 8, bottom: 8 },
         },
-        item: {
-            ...text(layer, "sans", { size: "sm" }),
-            secondaryTextSpacing: 10,
-            secondaryText: text(layer, "sans", { size: "sm" }),
-            padding: { left: 18, right: 18, top: 2, bottom: 2 },
-            hover: {
-                background: background(layer, "hovered"),
-                ...text(layer, "sans", "hovered", { size: "sm" }),
-            },
-            active: {
-                background: background(layer, "active"),
+        item: toggleable({
+            base: interactive({
+                base: {
+                    ...text(layer, "sans", { size: "sm" }),
+                    secondaryTextSpacing: 10,
+                    secondaryText: text(layer, "sans", { size: "sm" }),
+                    padding: { left: 18, right: 18, top: 2, bottom: 2 },
+                },
+                state: {
+                    hovered: {
+                        background: background(layer, "hovered"),
+                        ...text(layer, "sans", "hovered", { size: "sm" }),
+                    },
+                },
+            }),
+            state: {
+                active: {
+                    default: {
+                        background: background(layer, "active"),
+                    },
+                    hovered: {
+                        background: background(layer, "hovered"),
+                    },
+                },
             },
-            activeHover: {
-                background: background(layer, "active"),
-            },
-        },
+        }),
     }
 }

styles/src/styleTree/updateNotification.ts πŸ”—

@@ -1,5 +1,6 @@
 import { ColorScheme } from "../theme/colorScheme"
 import { foreground, text } from "./components"
+import { interactive } from "../element"
 
 const headerPadding = 8
 
@@ -10,22 +11,30 @@ export default function updateNotification(colorScheme: ColorScheme): Object {
             ...text(layer, "sans", { size: "xs" }),
             margin: { left: headerPadding, right: headerPadding },
         },
-        actionMessage: {
-            ...text(layer, "sans", { size: "xs" }),
-            margin: { left: headerPadding, top: 6, bottom: 6 },
-            hover: {
-                color: foreground(layer, "hovered"),
+        actionMessage: interactive({
+            base: {
+                ...text(layer, "sans", { size: "xs" }),
+                margin: { left: headerPadding, top: 6, bottom: 6 },
             },
-        },
-        dismissButton: {
-            color: foreground(layer),
-            iconWidth: 8,
-            iconHeight: 8,
-            buttonWidth: 8,
-            buttonHeight: 8,
-            hover: {
-                color: foreground(layer, "hovered"),
+            state: {
+                hovered: {
+                    color: foreground(layer, "hovered"),
+                },
             },
-        },
+        }),
+        dismissButton: interactive({
+            base: {
+                color: foreground(layer),
+                iconWidth: 8,
+                iconHeight: 8,
+                buttonWidth: 8,
+                buttonHeight: 8,
+            },
+            state: {
+                hovered: {
+                    color: foreground(layer, "hovered"),
+                },
+            },
+        }),
     }
 }

styles/src/styleTree/welcome.ts πŸ”—

@@ -8,6 +8,7 @@ import {
     TextProperties,
     svg,
 } from "./components"
+import { interactive } from "../element"
 
 export default function welcome(colorScheme: ColorScheme) {
     let layer = colorScheme.highest
@@ -63,27 +64,31 @@ export default function welcome(colorScheme: ColorScheme) {
                 bottom: 2,
             },
         },
-        button: {
-            background: background(layer),
-            border: border(layer, "active"),
-            cornerRadius: 4,
-            margin: {
-                top: 4,
-                bottom: 4,
-            },
-            padding: {
-                top: 3,
-                bottom: 3,
-                left: 7,
-                right: 7,
-            },
-            ...text(layer, "sans", "default", interactive_text_size),
-            hover: {
-                ...text(layer, "sans", "default", interactive_text_size),
-                background: background(layer, "hovered"),
+        button: interactive({
+            base: {
+                background: background(layer),
                 border: border(layer, "active"),
+                cornerRadius: 4,
+                margin: {
+                    top: 4,
+                    bottom: 4,
+                },
+                padding: {
+                    top: 3,
+                    bottom: 3,
+                    left: 7,
+                    right: 7,
+                },
+                ...text(layer, "sans", "default", interactive_text_size),
             },
-        },
+            state: {
+                hovered: {
+                    ...text(layer, "sans", "default", interactive_text_size),
+                    background: background(layer, "hovered"),
+                },
+            },
+        }),
+
         usageNote: {
             ...text(layer, "sans", "variant", { size: "2xs" }),
             padding: {

styles/src/styleTree/workspace.ts πŸ”—

@@ -10,42 +10,12 @@ import {
 } from "./components"
 import statusBar from "./statusBar"
 import tabBar from "./tabBar"
+import { interactive } from "../element"
 
+import { titlebar } from "./titlebar"
 export default function workspace(colorScheme: ColorScheme) {
     const layer = colorScheme.lowest
     const isLight = colorScheme.isLight
-    const itemSpacing = 8
-    const titlebarButton = {
-        cornerRadius: 6,
-        padding: {
-            top: 1,
-            bottom: 1,
-            left: 8,
-            right: 8,
-        },
-        ...text(layer, "sans", "variant", { size: "xs" }),
-        background: background(layer, "variant"),
-        border: border(layer),
-        hover: {
-            ...text(layer, "sans", "variant", "hovered", { size: "xs" }),
-            background: background(layer, "variant", "hovered"),
-            border: border(layer, "variant", "hovered"),
-        },
-        clicked: {
-            ...text(layer, "sans", "variant", "pressed", { size: "xs" }),
-            background: background(layer, "variant", "pressed"),
-            border: border(layer, "variant", "pressed"),
-        },
-        active: {
-            ...text(layer, "sans", "variant", "active", { size: "xs" }),
-            background: background(layer, "variant", "active"),
-            border: border(layer, "variant", "active"),
-        },
-    }
-    const avatarWidth = 18
-    const avatarOuterWidth = avatarWidth + 4
-    const followerAvatarWidth = 14
-    const followerAvatarOuterWidth = followerAvatarWidth + 4
 
     return {
         background: background(colorScheme.lowest),
@@ -78,19 +48,24 @@ export default function workspace(colorScheme: ColorScheme) {
                 },
                 cornerRadius: 4,
             },
-            keyboardHint: {
-                ...text(layer, "sans", "variant", { size: "sm" }),
-                padding: {
-                    top: 3,
-                    left: 8,
-                    right: 8,
-                    bottom: 3,
-                },
-                cornerRadius: 8,
-                hover: {
-                    ...text(layer, "sans", "active", { size: "sm" }),
-                },
-            },
+            keyboardHint: interactive({
+                base: {
+                    ...text(layer, "sans", "variant", { size: "sm" }),
+                    padding: {
+                        top: 3,
+                        left: 8,
+                        right: 8,
+                        bottom: 3,
+                    },
+                    cornerRadius: 8,
+                },
+                state: {
+                    hovered: {
+                        ...text(layer, "sans", "active", { size: "sm" }),
+                    },
+                },
+            }),
+
             keyboardHintWidth: 320,
         },
         joiningProjectAvatar: {
@@ -150,147 +125,51 @@ export default function workspace(colorScheme: ColorScheme) {
             width: 1,
         },
         statusBar: statusBar(colorScheme),
-        titlebar: {
-            itemSpacing,
-            facePileSpacing: 2,
-            height: 33, // 32px + 1px border. It's important the content area of the titlebar is evenly sized to vertically center avatar images.
-            background: background(layer),
-            border: border(layer, { bottom: true }),
-            padding: {
-                left: 80,
-                right: itemSpacing,
-            },
-
-            // Project
-            title: text(layer, "sans", "variant"),
-            highlight_color: text(layer, "sans", "active").color,
-
-            // Collaborators
-            leaderAvatar: {
-                width: avatarWidth,
-                outerWidth: avatarOuterWidth,
-                cornerRadius: avatarWidth / 2,
-                outerCornerRadius: avatarOuterWidth / 2,
-            },
-            followerAvatar: {
-                width: followerAvatarWidth,
-                outerWidth: followerAvatarOuterWidth,
-                cornerRadius: followerAvatarWidth / 2,
-                outerCornerRadius: followerAvatarOuterWidth / 2,
-            },
-            inactiveAvatarGrayscale: true,
-            followerAvatarOverlap: 8,
-            leaderSelection: {
-                margin: {
-                    top: 4,
-                    bottom: 4,
-                },
-                padding: {
-                    left: 2,
-                    right: 2,
-                    top: 2,
-                    bottom: 2,
-                },
-                cornerRadius: 6,
-            },
-            avatarRibbon: {
-                height: 3,
-                width: 12,
-                // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
-            },
-
-            // Sign in buttom
-            // FlatButton, Variant
-            signInPrompt: {
-                margin: {
-                    left: itemSpacing,
-                },
-                ...titlebarButton,
-            },
-
-            // Offline Indicator
-            offlineIcon: {
-                color: foreground(layer, "variant"),
-                width: 16,
-                margin: {
-                    left: itemSpacing,
-                },
-                padding: {
-                    right: 4,
-                },
-            },
-
-            // Notice that the collaboration server is out of date
-            outdatedWarning: {
-                ...text(layer, "sans", "warning", { size: "xs" }),
-                background: withOpacity(background(layer, "warning"), 0.3),
-                border: border(layer, "warning"),
-                margin: {
-                    left: itemSpacing,
-                },
-                padding: {
-                    left: 8,
-                    right: 8,
-                },
-                cornerRadius: 6,
-            },
-            callControl: {
-                cornerRadius: 6,
-                color: foreground(layer, "variant"),
-                iconWidth: 12,
-                buttonWidth: 20,
-                hover: {
-                    background: background(layer, "variant", "hovered"),
-                    color: foreground(layer, "variant", "hovered"),
-                },
-            },
-            toggleContactsButton: {
-                margin: { left: itemSpacing },
-                cornerRadius: 6,
-                color: foreground(layer, "variant"),
-                iconWidth: 14,
-                buttonWidth: 20,
-                active: {
-                    background: background(layer, "variant", "active"),
-                    color: foreground(layer, "variant", "active"),
-                },
-                clicked: {
-                    background: background(layer, "variant", "pressed"),
-                    color: foreground(layer, "variant", "pressed"),
-                },
-                hover: {
-                    background: background(layer, "variant", "hovered"),
-                    color: foreground(layer, "variant", "hovered"),
-                },
-            },
-            userMenuButton: {
-                buttonWidth: 20,
-                iconWidth: 12,
-                ...titlebarButton,
-            },
-            toggleContactsBadge: {
-                cornerRadius: 3,
-                padding: 2,
-                margin: { top: 3, left: 3 },
-                border: border(layer),
-                background: foreground(layer, "accent"),
-            },
-            shareButton: {
-                ...titlebarButton,
-            },
-        },
-
+        titlebar: titlebar(colorScheme),
         toolbar: {
             height: 34,
             background: background(colorScheme.highest),
             border: border(colorScheme.highest, { bottom: true }),
             itemSpacing: 8,
-            navButton: {
-                color: foreground(colorScheme.highest, "on"),
-                iconWidth: 12,
-                buttonWidth: 24,
+            navButton: interactive({
+                base: {
+                    color: foreground(colorScheme.highest, "on"),
+                    iconWidth: 12,
+                    buttonWidth: 24,
+                    cornerRadius: 6,
+                },
+                state: {
+                    hovered: {
+                        color: foreground(colorScheme.highest, "on", "hovered"),
+                        background: background(
+                            colorScheme.highest,
+                            "on",
+                            "hovered"
+                        ),
+                    },
+                    disabled: {
+                        color: foreground(
+                            colorScheme.highest,
+                            "on",
+                            "disabled"
+                        ),
+                    },
+                },
+            }),
+            padding: { left: 8, right: 8, top: 4, bottom: 4 },
+        },
+        breadcrumbHeight: 24,
+        breadcrumbs: interactive({
+            base: {
+                ...text(colorScheme.highest, "sans", "variant"),
                 cornerRadius: 6,
-                hover: {
+                padding: {
+                    left: 6,
+                    right: 6,
+                },
+            },
+            state: {
+                hovered: {
                     color: foreground(colorScheme.highest, "on", "hovered"),
                     background: background(
                         colorScheme.highest,
@@ -298,25 +177,8 @@ export default function workspace(colorScheme: ColorScheme) {
                         "hovered"
                     ),
                 },
-                disabled: {
-                    color: foreground(colorScheme.highest, "on", "disabled"),
-                },
-            },
-            padding: { left: 8, right: 8, top: 4, bottom: 4 },
-        },
-        breadcrumbHeight: 24,
-        breadcrumbs: {
-            ...text(colorScheme.highest, "sans", "variant"),
-            cornerRadius: 6,
-            padding: {
-                left: 6,
-                right: 6,
             },
-            hover: {
-                color: foreground(colorScheme.highest, "on", "hovered"),
-                background: background(colorScheme.highest, "on", "hovered"),
-            },
-        },
+        }),
         disconnectedOverlay: {
             ...text(layer, "sans"),
             background: withOpacity(background(layer), 0.8),

styles/src/theme/syntax.ts πŸ”—

@@ -129,8 +129,6 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax {
         [key: string]: Omit<SyntaxHighlightStyle, "color">
     } = {}
 
-    const light = colorScheme.isLight
-
     // then spread the default to each style
     for (const key of Object.keys({} as Syntax)) {
         syntax[key as keyof Syntax] = {

styles/src/theme/tokens/colorScheme.ts πŸ”—

@@ -1,9 +1,19 @@
-import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes } from "@tokens-studio/types"
-import { ColorScheme, Shadow, SyntaxHighlightStyle, ThemeSyntax } from "../colorScheme"
+import {
+    SingleBoxShadowToken,
+    SingleColorToken,
+    SingleOtherToken,
+    TokenTypes,
+} from "@tokens-studio/types"
+import {
+    ColorScheme,
+    Shadow,
+    SyntaxHighlightStyle,
+    ThemeSyntax,
+} from "../colorScheme"
 import { LayerToken, layerToken } from "./layer"
 import { PlayersToken, playersToken } from "./players"
 import { colorToken } from "./token"
-import { Syntax } from "../syntax";
+import { Syntax } from "../syntax"
 import editor from "../../styleTree/editor"
 
 interface ColorSchemeTokens {
@@ -18,27 +28,32 @@ interface ColorSchemeTokens {
     syntax?: Partial<ThemeSyntaxColorTokens>
 }
 
-const createShadowToken = (shadow: Shadow, tokenName: string): SingleBoxShadowToken => {
+const createShadowToken = (
+    shadow: Shadow,
+    tokenName: string
+): SingleBoxShadowToken => {
     return {
         name: tokenName,
         type: TokenTypes.BOX_SHADOW,
-        value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`
-    };
-};
+        value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`,
+    }
+}
 
 const popoverShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
-    const shadow = colorScheme.popoverShadow;
-    return createShadowToken(shadow, "popoverShadow");
-};
+    const shadow = colorScheme.popoverShadow
+    return createShadowToken(shadow, "popoverShadow")
+}
 
 const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
-    const shadow = colorScheme.modalShadow;
-    return createShadowToken(shadow, "modalShadow");
-};
+    const shadow = colorScheme.modalShadow
+    return createShadowToken(shadow, "modalShadow")
+}
 
 type ThemeSyntaxColorTokens = Record<keyof ThemeSyntax, SingleColorToken>
 
-function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens {
+function syntaxHighlightStyleColorTokens(
+    syntax: Syntax
+): ThemeSyntaxColorTokens {
     const styleKeys = Object.keys(syntax) as (keyof Syntax)[]
 
     return styleKeys.reduce((acc, styleKey) => {
@@ -46,13 +61,16 @@ function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens
         // This can happen because we have a "constructor" property on the syntax object
         // and a "constructor" property on the prototype of the syntax object
         // To work around this just assert that the type of the style is not a function
-        if (!syntax[styleKey] || typeof syntax[styleKey] === 'function') return acc;
-        const { color } = syntax[styleKey] as Required<SyntaxHighlightStyle>;
-        return { ...acc, [styleKey]: colorToken(styleKey, color) };
-    }, {} as ThemeSyntaxColorTokens);
+        if (!syntax[styleKey] || typeof syntax[styleKey] === "function")
+            return acc
+        const { color } = syntax[styleKey] as Required<SyntaxHighlightStyle>
+        return { ...acc, [styleKey]: colorToken(styleKey, color) }
+    }, {} as ThemeSyntaxColorTokens)
 }
 
-const syntaxTokens = (colorScheme: ColorScheme): ColorSchemeTokens['syntax'] => {
+const syntaxTokens = (
+    colorScheme: ColorScheme
+): ColorSchemeTokens["syntax"] => {
     const syntax = editor(colorScheme).syntax
 
     return syntaxHighlightStyleColorTokens(syntax)

styles/src/theme/tokens/layer.ts πŸ”—

@@ -1,11 +1,11 @@
-import { SingleColorToken } from "@tokens-studio/types";
-import { Layer, Style, StyleSet } from "../colorScheme";
-import { colorToken } from "./token";
+import { SingleColorToken } from "@tokens-studio/types"
+import { Layer, Style, StyleSet } from "../colorScheme"
+import { colorToken } from "./token"
 
 interface StyleToken {
-    background: SingleColorToken,
-    border: SingleColorToken,
-    foreground: SingleColorToken,
+    background: SingleColorToken
+    border: SingleColorToken
+    foreground: SingleColorToken
 }
 
 interface StyleSetToken {
@@ -37,24 +37,27 @@ export const styleToken = (style: Style, name: string): StyleToken => {
     return token
 }
 
-export const styleSetToken = (styleSet: StyleSet, name: string): StyleSetToken => {
-    const token: StyleSetToken = {} as StyleSetToken;
+export const styleSetToken = (
+    styleSet: StyleSet,
+    name: string
+): StyleSetToken => {
+    const token: StyleSetToken = {} as StyleSetToken
 
     for (const style in styleSet) {
-        const s = style as keyof StyleSet;
-        token[s] = styleToken(styleSet[s], `${name}${style}`);
+        const s = style as keyof StyleSet
+        token[s] = styleToken(styleSet[s], `${name}${style}`)
     }
 
-    return token;
+    return token
 }
 
 export const layerToken = (layer: Layer, name: string): LayerToken => {
-    const token: LayerToken = {} as LayerToken;
+    const token: LayerToken = {} as LayerToken
 
     for (const styleSet in layer) {
-        const s = styleSet as keyof Layer;
-        token[s] = styleSetToken(layer[s], `${name}${styleSet}`);
+        const s = styleSet as keyof Layer
+        token[s] = styleSetToken(layer[s], `${name}${styleSet}`)
     }
 
-    return token;
+    return token
 }

styles/src/theme/tokens/players.ts πŸ”—

@@ -6,13 +6,21 @@ export type PlayerToken = Record<"selection" | "cursor", SingleColorToken>
 
 export type PlayersToken = Record<keyof Players, PlayerToken>
 
-function buildPlayerToken(colorScheme: ColorScheme, index: number): PlayerToken {
-
+function buildPlayerToken(
+    colorScheme: ColorScheme,
+    index: number
+): PlayerToken {
     const playerNumber = index.toString() as keyof Players
 
     return {
-        selection: colorToken(`player${index}Selection`, colorScheme.players[playerNumber].selection),
-        cursor: colorToken(`player${index}Cursor`, colorScheme.players[playerNumber].cursor),
+        selection: colorToken(
+            `player${index}Selection`,
+            colorScheme.players[playerNumber].selection
+        ),
+        cursor: colorToken(
+            `player${index}Cursor`,
+            colorScheme.players[playerNumber].cursor
+        ),
     }
 }
 
@@ -24,5 +32,5 @@ export const playersToken = (colorScheme: ColorScheme): PlayersToken => ({
     "4": buildPlayerToken(colorScheme, 4),
     "5": buildPlayerToken(colorScheme, 5),
     "6": buildPlayerToken(colorScheme, 6),
-    "7": buildPlayerToken(colorScheme, 7)
+    "7": buildPlayerToken(colorScheme, 7),
 })

styles/src/theme/tokens/token.ts πŸ”—

@@ -1,6 +1,10 @@
 import { SingleColorToken, TokenTypes } from "@tokens-studio/types"
 
-export function colorToken(name: string, value: string, description?: string): SingleColorToken {
+export function colorToken(
+    name: string,
+    value: string,
+    description?: string
+): SingleColorToken {
     const token: SingleColorToken = {
         name,
         type: TokenTypes.COLOR,
@@ -8,7 +12,8 @@ export function colorToken(name: string, value: string, description?: string): S
         description,
     }
 
-    if (!token.value || token.value === '') throw new Error("Color token must have a value")
+    if (!token.value || token.value === "")
+        throw new Error("Color token must have a value")
 
     return token
 }

styles/src/themes/atelier/atelier-forest-light.ts πŸ”—

@@ -30,7 +30,7 @@ const getTheme = (variant: Variant): ThemeConfig => {
     return {
         name: `${meta.name} Forest Light`,
         author: meta.author,
-        appearance: ThemeAppearance.Dark,
+        appearance: ThemeAppearance.Light,
         licenseType: meta.licenseType,
         licenseUrl: meta.licenseUrl,
         licenseFile: `${__dirname}/LICENSE`,

styles/src/themes/rose-pine/common.ts πŸ”—

@@ -0,0 +1,75 @@
+import { ThemeSyntax } from "../../common"
+
+export const color = {
+    default: {
+        base: "#191724",
+        surface: "#1f1d2e",
+        overlay: "#26233a",
+        muted: "#6e6a86",
+        subtle: "#908caa",
+        text: "#e0def4",
+        love: "#eb6f92",
+        gold: "#f6c177",
+        rose: "#ebbcba",
+        pine: "#31748f",
+        foam: "#9ccfd8",
+        iris: "#c4a7e7",
+        highlightLow: "#21202e",
+        highlightMed: "#403d52",
+        highlightHigh: "#524f67",
+    },
+    moon: {
+        base: "#232136",
+        surface: "#2a273f",
+        overlay: "#393552",
+        muted: "#6e6a86",
+        subtle: "#908caa",
+        text: "#e0def4",
+        love: "#eb6f92",
+        gold: "#f6c177",
+        rose: "#ea9a97",
+        pine: "#3e8fb0",
+        foam: "#9ccfd8",
+        iris: "#c4a7e7",
+        highlightLow: "#2a283e",
+        highlightMed: "#44415a",
+        highlightHigh: "#56526e",
+    },
+    dawn: {
+        base: "#faf4ed",
+        surface: "#fffaf3",
+        overlay: "#f2e9e1",
+        muted: "#9893a5",
+        subtle: "#797593",
+        text: "#575279",
+        love: "#b4637a",
+        gold: "#ea9d34",
+        rose: "#d7827e",
+        pine: "#286983",
+        foam: "#56949f",
+        iris: "#907aa9",
+        highlightLow: "#f4ede8",
+        highlightMed: "#dfdad9",
+        highlightHigh: "#cecacd",
+    },
+}
+
+export const syntax = (c: typeof color.default): Partial<ThemeSyntax> => {
+    return {
+        comment: { color: c.muted },
+        operator: { color: c.pine },
+        punctuation: { color: c.subtle },
+        variable: { color: c.text },
+        string: { color: c.gold },
+        type: { color: c.foam },
+        "type.builtin": { color: c.foam },
+        boolean: { color: c.rose },
+        function: { color: c.rose },
+        keyword: { color: c.pine },
+        tag: { color: c.foam },
+        "function.method": { color: c.rose },
+        title: { color: c.gold },
+        linkText: { color: c.foam, italic: false },
+        linkUri: { color: c.rose },
+    }
+}

styles/src/themes/rose-pine/rose-pine-dawn.ts πŸ”—

@@ -6,6 +6,13 @@ import {
     ThemeConfig,
 } from "../../common"
 
+import { color as c, syntax } from "./common"
+
+const color = c.dawn
+
+const green = chroma.mix(color.foam, "#10b981", 0.6, "lab")
+const magenta = chroma.mix(color.love, color.pine, 0.5, "lab")
+
 export const theme: ThemeConfig = {
     name: "RosΓ© Pine Dawn",
     author: "edunfelt",
@@ -15,25 +22,28 @@ export const theme: ThemeConfig = {
     licenseFile: `${__dirname}/LICENSE`,
     inputColor: {
         neutral: chroma
-            .scale([
-                "#575279",
-                "#797593",
-                "#9893A5",
-                "#B5AFB8",
-                "#D3CCCC",
-                "#F2E9E1",
-                "#FFFAF3",
-                "#FAF4ED",
-            ])
+            .scale(
+                [
+                    color.base,
+                    color.surface,
+                    color.highlightHigh,
+                    color.overlay,
+                    color.muted,
+                    color.subtle,
+                    color.text,
+                ].reverse()
+            )
             .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
-        red: colorRamp(chroma("#B4637A")),
-        orange: colorRamp(chroma("#D7827E")),
-        yellow: colorRamp(chroma("#EA9D34")),
-        green: colorRamp(chroma("#679967")),
-        cyan: colorRamp(chroma("#286983")),
-        blue: colorRamp(chroma("#56949F")),
-        violet: colorRamp(chroma("#907AA9")),
-        magenta: colorRamp(chroma("#79549F")),
+        red: colorRamp(chroma(color.love)),
+        orange: colorRamp(chroma(color.iris)),
+        yellow: colorRamp(chroma(color.gold)),
+        green: colorRamp(chroma(green)),
+        cyan: colorRamp(chroma(color.pine)),
+        blue: colorRamp(chroma(color.foam)),
+        violet: colorRamp(chroma(color.iris)),
+        magenta: colorRamp(chroma(magenta)),
+    },
+    override: {
+        syntax: syntax(color),
     },
-    override: { syntax: {} },
 }

styles/src/themes/rose-pine/rose-pine-moon.ts πŸ”—

@@ -6,6 +6,13 @@ import {
     ThemeConfig,
 } from "../../common"
 
+import { color as c, syntax } from "./common"
+
+const color = c.moon
+
+const green = chroma.mix(color.foam, "#10b981", 0.6, "lab")
+const magenta = chroma.mix(color.love, color.pine, 0.5, "lab")
+
 export const theme: ThemeConfig = {
     name: "RosΓ© Pine Moon",
     author: "edunfelt",
@@ -16,24 +23,25 @@ export const theme: ThemeConfig = {
     inputColor: {
         neutral: chroma
             .scale([
-                "#232136",
-                "#2A273F",
-                "#393552",
-                "#3E3A53",
-                "#56526C",
-                "#6E6A86",
-                "#908CAA",
-                "#E0DEF4",
+                color.base,
+                color.surface,
+                color.highlightHigh,
+                color.overlay,
+                color.muted,
+                color.subtle,
+                color.text,
             ])
             .domain([0, 0.3, 0.55, 1]),
-        red: colorRamp(chroma("#EB6F92")),
-        orange: colorRamp(chroma("#EBBCBA")),
-        yellow: colorRamp(chroma("#F6C177")),
-        green: colorRamp(chroma("#8DBD8D")),
-        cyan: colorRamp(chroma("#409BBE")),
-        blue: colorRamp(chroma("#9CCFD8")),
-        violet: colorRamp(chroma("#C4A7E7")),
-        magenta: colorRamp(chroma("#AB6FE9")),
+        red: colorRamp(chroma(color.love)),
+        orange: colorRamp(chroma(color.iris)),
+        yellow: colorRamp(chroma(color.gold)),
+        green: colorRamp(chroma(green)),
+        cyan: colorRamp(chroma(color.pine)),
+        blue: colorRamp(chroma(color.foam)),
+        violet: colorRamp(chroma(color.iris)),
+        magenta: colorRamp(chroma(magenta)),
+    },
+    override: {
+        syntax: syntax(color),
     },
-    override: { syntax: {} },
 }

styles/src/themes/rose-pine/rose-pine.ts πŸ”—

@@ -5,6 +5,12 @@ import {
     ThemeLicenseType,
     ThemeConfig,
 } from "../../common"
+import { color as c, syntax } from "./common"
+
+const color = c.default
+
+const green = chroma.mix(color.foam, "#10b981", 0.6, "lab")
+const magenta = chroma.mix(color.love, color.pine, 0.5, "lab")
 
 export const theme: ThemeConfig = {
     name: "RosΓ© Pine",
@@ -15,23 +21,24 @@ export const theme: ThemeConfig = {
     licenseFile: `${__dirname}/LICENSE`,
     inputColor: {
         neutral: chroma.scale([
-            "#191724",
-            "#1f1d2e",
-            "#26233A",
-            "#3E3A53",
-            "#56526C",
-            "#6E6A86",
-            "#908CAA",
-            "#E0DEF4",
+            color.base,
+            color.surface,
+            color.highlightHigh,
+            color.overlay,
+            color.muted,
+            color.subtle,
+            color.text,
         ]),
-        red: colorRamp(chroma("#EB6F92")),
-        orange: colorRamp(chroma("#EBBCBA")),
-        yellow: colorRamp(chroma("#F6C177")),
-        green: colorRamp(chroma("#8DBD8D")),
-        cyan: colorRamp(chroma("#409BBE")),
-        blue: colorRamp(chroma("#9CCFD8")),
-        violet: colorRamp(chroma("#C4A7E7")),
-        magenta: colorRamp(chroma("#AB6FE9")),
+        red: colorRamp(chroma(color.love)),
+        orange: colorRamp(chroma(color.iris)),
+        yellow: colorRamp(chroma(color.gold)),
+        green: colorRamp(chroma(green)),
+        cyan: colorRamp(chroma(color.pine)),
+        blue: colorRamp(chroma(color.foam)),
+        violet: colorRamp(chroma(color.iris)),
+        magenta: colorRamp(chroma(magenta)),
+    },
+    override: {
+        syntax: syntax(color),
     },
-    override: { syntax: {} },
 }

styles/src/utils/slugify.ts πŸ”—

@@ -1 +1,10 @@
-export function slugify(t: string): string { return t.toString().toLowerCase().replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-').replace(/^-+/, '').replace(/-+$/, '') }
+export function slugify(t: string): string {
+    return t
+        .toString()
+        .toLowerCase()
+        .replace(/\s+/g, "-")
+        .replace(/[^\w\-]+/g, "")
+        .replace(/\-\-+/g, "-")
+        .replace(/^-+/, "")
+        .replace(/-+$/, "")
+}

styles/tsconfig.json πŸ”—

@@ -20,7 +20,17 @@
         "noFallthroughCasesInSwitch": false,
         "experimentalDecorators": true,
         "strictPropertyInitialization": false,
-        "skipLibCheck": true
+        "skipLibCheck": true,
+        "baseUrl": ".",
+        "paths": {
+            "@/*": ["./*"],
+            "@element/*": ["./src/element/*"],
+            "@component/*": ["./src/component/*"],
+            "@styleTree/*": ["./src/styleTree/*"],
+            "@theme/*": ["./src/theme/*"],
+            "@themes/*": ["./src/themes/*"],
+            "@util/*": ["./src/util/*"]
+        }
     },
     "exclude": ["node_modules"]
 }

styles/vitest.config.ts πŸ”—

@@ -0,0 +1,8 @@
+import { configDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+    test: {
+        exclude: [...configDefaults.exclude, "target/*"],
+        include: ["src/**/*.{spec,test}.ts"],
+    },
+})