From f156a844213de4adaf1d4608282e73e9da183118 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Wed, 14 Oct 2026 11:57:31 +0200 Subject: [PATCH] chore(web): delete the legacy React app Production and the desktop shell already ship apps/web-foldkit, which still reached into apps/web through the `~` alias, its stylesheet and public/. - Move the framework-free modules Foldkit uses (collections, actions, theme, RPC/API clients, notifications, *.styles.ts, styles, public/) into apps/web-foldkit/src/core and apps/web-foldkit/public; `~/` now resolves there. Drop their React-only bits (avatar context, unread selectors, slate-react/react-hotkeys types). - Delete apps/web and libs/tanstack-db-atom (only the React app used it), plus the icon/illustration generators that rendered React components. - Move the web alchemy definition into apps/web-foldkit, point setup's env file there, run Foldkit's tests from the root Vitest projects. - ui-parity: drop the legacy-head target; coverage reads routes from the pinned baseline ref. Co-Authored-By: Claude Opus 5.5 --- .oxlintrc.json | 16 +- CLAUDE.md | 56 +- alchemy.run.ts | 2 +- apps/{web => web-foldkit}/alchemy.run.ts | 14 +- apps/web-foldkit/package.json | 18 +- .../public/apple-touch-icon-180x180.png | Bin .../public/brands/openstatus.svg | 0 apps/{web => web-foldkit}/public/favicon.ico | Bin apps/{web => web-foldkit}/public/icon.svg | 0 .../public/images/image-mask.webp | Bin .../public/images/masks/circle.webp | Bin .../public/images/masks/square.webp | Bin .../public/images/onboarding/autumn-day.webp | Bin .../images/onboarding/autumn-night.webp | Bin .../public/images/onboarding/spring-day.webp | Bin .../images/onboarding/spring-night.webp | Bin .../public/images/onboarding/summer-day.webp | Bin .../images/onboarding/summer-night.webp | Bin .../public/images/onboarding/winter-day.webp | Bin .../images/onboarding/winter-night.webp | Bin .../public/images/squirrle_ocean.webp | Bin .../public/images/squirrle_window.webp | Bin .../{web => web-foldkit}/public/manifest.json | 0 .../public/maskable-icon-512x512.png | Bin .../public/pwa-192x192.png | Bin .../public/pwa-512x512.png | Bin .../{web => web-foldkit}/public/pwa-64x64.png | Bin apps/{web => web-foldkit}/public/robots.txt | 0 .../public/sounds/README.md | 4 +- .../public/sounds/keyboard/.gitkeep | 0 .../public/sounds/keyboard/README.md | 0 .../public/sounds/notification01.mp3 | Bin .../public/sounds/notification03.mp3 | Bin .../generate-appearance-illustrations.ts | 39 - .../scripts/generate-file-icons.ts | 137 - apps/web-foldkit/scripts/generate-icons.ts | 93 - apps/web-foldkit/src/chat/file-icons.ts | 2 +- .../src/core}/atoms/emoji-atoms.ts | 0 .../atoms/notification-sound-atoms.test.ts | 0 .../core}/atoms/notification-sound-atoms.ts | 0 .../chat/slate-editor/detect-language.ts | 0 .../chat/slate-editor/slate-code-decorator.ts | 0 .../slate-editor/slate-emoji-decorator.ts | 0 .../slate-markdown-decorations.ts | 0 .../slate-markdown-serializer.test.ts | 0 .../slate-editor/slate-markdown-serializer.ts | 0 .../slate-message-viewer-model.ts | 0 .../components/chat/slate-editor/types.ts | 4 +- .../embeds/embed-markdown.parser.ts | 0 .../embeds/embed-markdown.styles.ts | 0 .../core}/components/embeds/embed.styles.ts | 0 .../core}/components/link-preview.utils.ts | 0 .../src/core}/components/ui/avatar/styles.ts | 16 +- .../src/core}/components/ui/badge.styles.ts | 0 .../src/core}/components/ui/button.styles.ts | 0 .../core}/components/ui/calendar.styles.ts | 0 .../src/core}/components/ui/card.styles.ts | 0 .../core}/components/ui/checkbox.styles.ts | 0 .../core}/components/ui/choice-box.styles.ts | 0 .../core}/components/ui/combo-box.styles.ts | 0 .../components/ui/command-menu-form.styles.ts | 0 .../components/ui/command-menu.styles.ts | 0 .../components/ui/context-menu.styles.ts | 0 .../core}/components/ui/date-field.styles.ts | 0 .../core}/components/ui/date-picker.styles.ts | 0 .../src/core}/components/ui/dialog.styles.ts | 0 .../core}/components/ui/dropdown.styles.ts | 0 .../core}/components/ui/empty-state.styles.ts | 0 .../ui/field-validation-state.styles.ts | 0 .../src/core}/components/ui/field.styles.ts | 0 .../ui/form-error-summary.styles.ts | 0 .../core}/components/ui/input-otp.styles.ts | 0 .../src/core}/components/ui/input.styles.ts | 0 .../core}/components/ui/keyboard.styles.ts | 0 .../src/core}/components/ui/link.styles.ts | 0 .../core}/components/ui/list-box.styles.ts | 0 .../src/core}/components/ui/loader.styles.ts | 0 .../src/core}/components/ui/menu.styles.ts | 0 .../src/core}/components/ui/modal.styles.ts | 0 .../src/core}/components/ui/popover.styles.ts | 0 .../components/ui/progress-bar.styles.ts | 0 .../src/core}/components/ui/radio.styles.ts | 0 .../components/ui/search-field.styles.ts | 0 .../components/ui/section-footer.styles.ts | 0 .../components/ui/section-header.styles.ts | 0 .../components/ui/section-label.styles.ts | 0 .../src/core}/components/ui/select.styles.ts | 0 .../core}/components/ui/separator.styles.ts | 0 .../src/core}/components/ui/sheet.styles.ts | 0 .../src/core}/components/ui/slider.styles.ts | 0 .../src/core}/components/ui/sonner.styles.ts | 0 .../ui/split-panel/split-panel.styles.ts | 0 .../src/core}/components/ui/switch.styles.ts | 0 .../src/core}/components/ui/table.styles.ts | 0 .../src/core}/components/ui/tabs.styles.ts | 0 .../src/core}/components/ui/text.styles.ts | 0 .../core}/components/ui/textarea.styles.ts | 0 .../src/core}/components/ui/toast.styles.ts | 0 .../components/ui/toggle-group.styles.ts | 0 .../src/core}/components/ui/toggle.styles.ts | 0 .../src/core}/components/ui/toolbar.styles.ts | 0 .../src/core}/components/ui/tooltip.styles.ts | 0 .../src/core}/components/ui/tree.styles.ts | 0 .../src/core}/db/actions.ts | 2 +- .../src/core}/db/collections.ts | 2 +- .../src/core}/dev-gallery/gallery.styles.ts | 0 .../src/core}/lib/access-token.test.ts | 1 + .../src/core}/lib/access-token.ts | 0 .../src/core}/lib/auth-fetch.ts | 0 .../src/core}/lib/bot-avatar.ts | 0 .../src/core}/lib/bot-scopes.ts | 0 .../core}/lib/connect-shared-channels.test.ts | 0 .../src/core}/lib/connect-shared-channels.ts | 0 .../src/core}/lib/custom-emoji-utils.ts | 0 .../src/core}/lib/date-fns-shared.ts | 0 .../src/core}/lib/electric-fetch.ts | 0 .../src/core}/lib/emoji-shortcode-map.ts | 0 .../src/core}/lib/emoji-utils.ts | 0 .../src/core}/lib/error-messages.ts | 2 +- .../src/core}/lib/helper/generate-shades.ts | 0 .../core}/lib/hotkeys/hotkey-registry.test.ts | 2 +- .../src/core}/lib/hotkeys/hotkey-registry.ts | 2 +- .../src/core}/lib/integrations/__data.ts | 0 .../core}/lib/native-notifications.test.ts | 1 + .../src/core}/lib/native-notifications.ts | 0 .../lib/notification-sound-manager.test.ts | 0 .../core}/lib/notification-sound-manager.ts | 0 .../src/core}/lib/notifications/README.md | 1 - .../lib/notifications/diagnostics-store.ts | 0 .../src/core}/lib/notifications/index.ts | 6 - .../lib/notifications/orchestrator.test.ts | 0 .../core}/lib/notifications/orchestrator.ts | 0 .../core}/lib/notifications/sinks/in-app.ts | 0 .../core}/lib/notifications/sinks/native.ts | 0 .../core}/lib/notifications/sinks/sound.ts | 0 .../src/core}/lib/notifications/types.ts | 0 .../src/core}/lib/platform-storage/index.ts | 0 .../platform-key-value-store.ts | 0 .../lib/platform-storage/platform-runtime.ts | 0 .../platform-storage/tauri-key-value-store.ts | 0 .../src/core}/lib/rivet-client.ts | 0 .../src/core}/lib/rpc-auth-middleware.ts | 0 .../src/core}/lib/search-filter-parser.ts | 0 .../core}/lib/services/common/api-client.ts | 0 .../core}/lib/services/common/atom-client.ts | 0 .../lib/services/common/rpc-atom-client.ts | 0 .../src/core}/lib/services/common/runtime.ts | 0 .../core}/lib/services/common/telemetry.ts | 0 .../src/core}/lib/session-bearer.ts | 0 .../src/core}/lib/tauri-autostart.ts | 0 .../src => web-foldkit/src/core}/lib/tauri.ts | 0 .../src/core}/lib/theme/apply.ts | 0 .../src/core}/lib/theme/presets.ts | 0 .../src/core}/lib/theme/remix.ts | 0 .../src/core}/lib/upload-to-storage.ts | 0 .../src => web-foldkit/src/core}/lib/utils.ts | 0 .../src/core}/styles/code-syntax.css | 0 .../src/core}/styles/styles.css | 2 - .../src/core}/styles/theme.css | 0 .../src/core}/utils/attachment-url.ts | 0 .../src => web-foldkit/src/core}/utils/cx.ts | 0 .../src/core}/utils/file-utils.ts | 0 .../src/core}/utils/image-crop.ts | 0 .../src/core}/utils/presence.test.ts | 0 .../src/core}/utils/presence.ts | 0 .../src/core}/utils/status.ts | 0 .../src/core}/utils/timezone.ts | 0 apps/web-foldkit/src/icons/index.ts | 2 +- .../my-settings/appearance/illustrations.ts | 2 +- apps/web-foldkit/src/styles.css | 7 +- .../web-foldkit/src/ui/appearance-previews.ts | 2 +- apps/web-foldkit/tsconfig.json | 2 +- apps/web-foldkit/vite.config.ts | 6 +- apps/web/.cta.json | 11 - apps/web/.env.example | 26 - apps/web/.gitignore | 10 - apps/web/README.md | 34 - apps/web/TODO.md | 12 - apps/web/components.json | 30 - apps/web/index.html | 17 - apps/web/package.json | 118 - apps/web/src/atoms/bot-atoms.ts | 92 - apps/web/src/atoms/channel-atoms.ts | 22 - apps/web/src/atoms/channel-member-atoms.ts | 23 - apps/web/src/atoms/channel-webhook-atoms.ts | 44 - apps/web/src/atoms/chat-atoms.ts | 70 - apps/web/src/atoms/chat-query-atoms.ts | 34 - apps/web/src/atoms/command-palette-state.ts | 170 -- apps/web/src/atoms/connect-share-atoms.ts | 27 - apps/web/src/atoms/custom-emoji-atoms.ts | 35 - apps/web/src/atoms/feature-discovery-atoms.ts | 31 - .../src/atoms/github-subscription-atoms.ts | 39 - apps/web/src/atoms/hotkey-atoms.test.ts | 44 - apps/web/src/atoms/hotkey-atoms.ts | 143 -- apps/web/src/atoms/loading-state-atoms.ts | 112 - apps/web/src/atoms/message-atoms.ts | 176 -- apps/web/src/atoms/modal-atoms.ts | 85 - apps/web/src/atoms/onboarding-atoms.ts | 154 -- apps/web/src/atoms/organization-atoms.ts | 44 - apps/web/src/atoms/panel-atoms.ts | 102 - apps/web/src/atoms/presence-atoms.ts | 19 - apps/web/src/atoms/react-scan-atoms.ts | 10 - apps/web/src/atoms/recent-channels-atom.ts | 31 - apps/web/src/atoms/rss-subscription-atoms.ts | 39 - apps/web/src/atoms/search-atoms.ts | 43 - apps/web/src/atoms/section-collapse-atoms.ts | 31 - apps/web/src/atoms/sidebar-atoms.ts | 69 - apps/web/src/atoms/tauri-update-atoms.test.ts | 95 - apps/web/src/atoms/tauri-update-atoms.ts | 241 -- apps/web/src/atoms/typing-indicator-atom.ts | 13 - apps/web/src/atoms/user-atoms.ts | 13 - apps/web/src/components/app-nav.tsx | 45 - .../src/components/bots/bot-avatar-upload.tsx | 195 -- apps/web/src/components/bots/bot-avatar.tsx | 39 - apps/web/src/components/bots/bot-card.tsx | 304 --- .../src/components/bots/bot-token-display.tsx | 64 - .../components/bots/marketplace-bot-card.tsx | 106 - apps/web/src/components/channel-icon.tsx | 18 - .../add-github-repo-modal.tsx | 322 --- .../channel-settings/add-rss-feed-modal.tsx | 158 -- .../channel-settings/create-webhook-form.tsx | 179 -- .../github-integration-card.tsx | 171 -- .../github-subscription-card.tsx | 171 -- .../channel-settings/integration-card.tsx | 306 --- .../channel-settings/maple-section.tsx | 307 --- .../channel-settings/openstatus-section.tsx | 326 --- .../channel-settings/railway-section.tsx | 326 --- .../channel-settings/rss-integration-card.tsx | 268 -- .../channel-settings/token-display.tsx | 121 - .../chat-sync/add-channel-link-modal.tsx | 380 --- .../chat-sync/add-connection-modal.tsx | 235 -- .../src/components/chat/agent-steps-view.tsx | 795 ------ .../components/chat/author-identity.test.ts | 82 - .../src/components/chat/author-identity.ts | 52 - .../channel-files-documents-list.tsx | 106 - .../channel-files/channel-files-header.tsx | 58 - .../channel-files-media-gallery-view.tsx | 190 -- .../channel-files-media-grid.tsx | 226 -- .../chat/channel-files/channel-files-view.tsx | 103 - .../components/chat/channel-files/index.ts | 1 - .../components/chat/channel-join-banner.tsx | 86 - apps/web/src/components/chat/chat-header.tsx | 209 -- apps/web/src/components/chat/chat-tab-bar.tsx | 57 - .../chat/composer/composer-actions.tsx | 103 - .../composer/composer-attachment-previews.tsx | 102 - .../chat/composer/composer-context.tsx | 117 - .../chat/composer/composer-drop-zone.tsx | 101 - .../chat/composer/composer-edit-indicator.tsx | 49 - .../chat/composer/composer-editor.tsx | 154 -- .../chat/composer/composer-frame.tsx | 29 - .../composer/composer-reply-indicator.tsx | 27 - .../src/components/chat/composer/index.tsx | 64 - .../components/chat/delete-message-modal.tsx | 42 - .../components/chat/image-viewer-modal.tsx | 303 --- apps/web/src/components/chat/index.ts | 12 - .../components/chat/inline-thread-preview.tsx | 161 -- .../components/chat/message-attachments.tsx | 208 -- .../chat/message-composer-actions.tsx | 91 - .../src/components/chat/message-content.tsx | 342 --- .../components/chat/message-context-menu.tsx | 170 -- .../src/components/chat/message-embeds.tsx | 121 - apps/web/src/components/chat/message-item.tsx | 116 - apps/web/src/components/chat/message-list.tsx | 365 --- .../components/chat/message-live-context.tsx | 51 - .../chat/message-live-state.test.tsx | 93 - .../components/chat/message-live-state.tsx | 254 -- .../components/chat/message-reply-section.tsx | 87 - .../src/components/chat/message-toolbar.tsx | 175 -- apps/web/src/components/chat/message.tsx | 492 ---- .../components/chat/pinned-messages-modal.tsx | 182 -- .../src/components/chat/reaction-button.tsx | 133 - .../src/components/chat/reply-indicator.tsx | 61 - .../autocomplete/autocomplete-listbox.tsx | 156 -- .../autocomplete/command-input-panel.tsx | 117 - .../autocomplete/editor-autocomplete.tsx | 74 - .../autocomplete/hooks/use-bot-commands.ts | 65 - .../chat/slate-editor/autocomplete/index.ts | 21 - .../autocomplete/slate-autocomplete-plugin.ts | 275 -- .../triggers/bot-command-item.tsx | 57 - .../autocomplete/triggers/command-trigger.tsx | 60 - .../autocomplete/triggers/emoji-trigger.tsx | 122 - .../autocomplete/triggers/mention-trigger.tsx | 213 -- .../chat/slate-editor/autocomplete/types.ts | 157 -- .../autocomplete/use-slate-combobox.tsx | 101 - .../chat/slate-editor/code-block-element.tsx | 123 - .../slate-editor/custom-emoji-element.tsx | 47 - .../chat/slate-editor/heading-element.tsx | 38 - .../chat/slate-editor/link-leaf.tsx | 86 - .../chat/slate-editor/mention-element.tsx | 146 -- .../chat/slate-editor/mention-leaf.tsx | 31 - .../slate-code-block-paste-plugin.ts | 46 - .../slate-editor/slate-file-paste-plugin.ts | 89 - .../slate-markdown-decorators.tsx | 91 - .../slate-editor/slate-mention-plugin.tsx | 270 -- .../slate-editor/slate-message-composer.tsx | 53 - .../slate-editor/slate-message-editor.tsx | 1158 --------- .../slate-editor/slate-message-viewer.tsx | 204 -- .../chat/slate-editor/table-element.tsx | 58 - .../components/chat/streaming-markdown.tsx | 70 - .../components/chat/thread-message-item.tsx | 65 - .../components/chat/thread-message-list.tsx | 162 -- apps/web/src/components/chat/thread-panel.tsx | 249 -- .../src/components/chat/typing-indicator.tsx | 35 - .../components/chat/user-profile-popover.tsx | 298 --- apps/web/src/components/chat/video-player.tsx | 607 ----- apps/web/src/components/city-card.tsx | 106 - apps/web/src/components/command-palette.tsx | 10 - .../command-palette-context.tsx | 53 - .../command-palette/command-palette-root.tsx | 148 -- .../command-palette/markdown-text.tsx | 223 -- .../command-palette/pages/appearance-view.tsx | 98 - .../pages/create-channel-view.tsx | 156 -- .../command-palette/pages/home-view.tsx | 504 ---- .../components/command-palette/pages/index.ts | 5 - .../pages/join-channel-view.tsx | 153 -- .../command-palette/pages/status-view.tsx | 47 - .../command-palette/search-filter-chip.tsx | 62 - .../search-filter-decorator.tsx | 92 - .../command-palette/search-result-item.tsx | 118 - .../command-palette/search-slate-editor.tsx | 402 --- .../command-palette/search-view.tsx | 417 --- .../connect/share-channel-modal.test.tsx | 199 -- .../connect/share-channel-modal.tsx | 272 -- .../src/components/embeds/embed-author.tsx | 84 - apps/web/src/components/embeds/embed-body.tsx | 72 - .../embeds/embed-connect-prompt.tsx | 82 - .../web/src/components/embeds/embed-error.tsx | 74 - .../src/components/embeds/embed-fields.tsx | 69 - .../src/components/embeds/embed-footer.tsx | 46 - .../web/src/components/embeds/embed-image.tsx | 24 - .../src/components/embeds/embed-markdown.tsx | 101 - .../src/components/embeds/embed-skeleton.tsx | 66 - .../src/components/embeds/embed-thumbnail.tsx | 25 - apps/web/src/components/embeds/embed.tsx | 67 - apps/web/src/components/embeds/index.ts | 64 - .../src/components/embeds/use-embed-theme.ts | 104 - .../emoji-picker/custom-emoji-section.tsx | 63 - .../emoji-picker/emoji-picker-content.tsx | 83 - .../emoji-picker/emoji-picker-dialog.tsx | 78 - .../emoji-picker/emoji-picker-footer.tsx | 37 - .../emoji-picker/emoji-picker-search.tsx | 27 - .../components/emoji-picker/emoji-picker.tsx | 20 - apps/web/src/components/emoji-picker/index.ts | 6 - apps/web/src/components/emoji-preview.tsx | 31 - apps/web/src/components/gif-embed.tsx | 48 - .../gif-picker/gif-picker-attribution.tsx | 14 - .../gif-picker/gif-picker-categories.tsx | 37 - .../gif-picker/gif-picker-dialog.tsx | 86 - .../components/gif-picker/gif-picker-grid.tsx | 92 - .../gif-picker/gif-picker-search.tsx | 16 - apps/web/src/components/gif-picker/index.ts | 1 - .../src/components/gif-picker/use-klipy.ts | 163 -- apps/web/src/components/globe-visual.tsx | 170 -- .../src/components/icons/icon-arrow-left.tsx | 41 - .../src/components/icons/icon-arrow-path.tsx | 41 - .../src/components/icons/icon-bell-slash.tsx | 52 - apps/web/src/components/icons/icon-bell.tsx | 41 - .../components/icons/icon-brain-sparkle.tsx | 42 - apps/web/src/components/icons/icon-branch.tsx | 30 - .../src/components/icons/icon-changelog.tsx | 47 - .../src/components/icons/icon-chart-bar.tsx | 58 - .../src/components/icons/icon-chat-bubble.tsx | 37 - apps/web/src/components/icons/icon-check.tsx | 29 - .../components/icons/icon-chevron-down.tsx | 29 - .../components/icons/icon-chevron-left.tsx | 29 - .../components/icons/icon-chevron-right.tsx | 29 - .../components/icons/icon-chevron-up-down.tsx | 41 - .../src/components/icons/icon-chevron-up.tsx | 29 - .../icons/icon-circle-dotted-user.tsx | 102 - .../components/icons/icon-circle-pause.tsx | 41 - .../src/components/icons/icon-circle-plus.tsx | 35 - apps/web/src/components/icons/icon-clock.tsx | 40 - apps/web/src/components/icons/icon-close.tsx | 33 - apps/web/src/components/icons/icon-code.tsx | 39 - .../web/src/components/icons/icon-company.tsx | 53 - .../web/src/components/icons/icon-connect.tsx | 57 - apps/web/src/components/icons/icon-copy.tsx | 39 - apps/web/src/components/icons/icon-cube.tsx | 39 - .../src/components/icons/icon-dashboard.tsx | 55 - .../web/src/components/icons/icon-discord.tsx | 26 - .../components/icons/icon-dots-vertical.tsx | 28 - apps/web/src/components/icons/icon-dots.tsx | 28 - .../src/components/icons/icon-download.tsx | 41 - apps/web/src/components/icons/icon-edit.tsx | 43 - .../web/src/components/icons/icon-emoji-1.tsx | 50 - .../src/components/icons/icon-emoji-add.tsx | 56 - .../src/components/icons/icon-envelope.tsx | 44 - .../src/components/icons/icon-eye-slash.tsx | 50 - apps/web/src/components/icons/icon-eye.tsx | 36 - .../src/components/icons/icon-folder-plus.tsx | 45 - apps/web/src/components/icons/icon-folder.tsx | 39 - apps/web/src/components/icons/icon-gear.tsx | 37 - apps/web/src/components/icons/icon-gif.tsx | 48 - .../icons/icon-grid-circle-plus.tsx | 50 - .../web/src/components/icons/icon-hashtag.tsx | 63 - apps/web/src/components/icons/icon-heart.tsx | 32 - .../src/components/icons/icon-integratio-.tsx | 56 - apps/web/src/components/icons/icon-leave.tsx | 44 - .../src/components/icons/icon-lightbulb.tsx | 70 - .../components/icons/icon-link-external.tsx | 41 - apps/web/src/components/icons/icon-loader.tsx | 64 - apps/web/src/components/icons/icon-lock.tsx | 43 - apps/web/src/components/icons/icon-logout.tsx | 42 - .../icons/icon-magic-wand-sparkle.tsx | 55 - .../src/components/icons/icon-magnifier-3.tsx | 48 - .../web/src/components/icons/icon-map-pin.tsx | 46 - .../src/components/icons/icon-megaphone.tsx | 49 - apps/web/src/components/icons/icon-menu.tsx | 37 - apps/web/src/components/icons/icon-minus.tsx | 29 - apps/web/src/components/icons/icon-moon.tsx | 34 - .../src/components/icons/icon-msg-clock.tsx | 42 - apps/web/src/components/icons/icon-msgs.tsx | 38 - .../src/components/icons/icon-paintbrush.tsx | 42 - .../src/components/icons/icon-paperclip2.tsx | 38 - .../src/components/icons/icon-pen-sparkle.tsx | 43 - .../src/components/icons/icon-persons-2.tsx | 52 - apps/web/src/components/icons/icon-phone.tsx | 33 - apps/web/src/components/icons/icon-pin.tsx | 37 - apps/web/src/components/icons/icon-play.tsx | 40 - apps/web/src/components/icons/icon-plus.tsx | 38 - .../icons/icon-presentation-chart.tsx | 50 - apps/web/src/components/icons/icon-reply.tsx | 37 - apps/web/src/components/icons/icon-robot.tsx | 47 - apps/web/src/components/icons/icon-rocket.tsx | 51 - .../web/src/components/icons/icon-servers.tsx | 53 - apps/web/src/components/icons/icon-share.tsx | 37 - apps/web/src/components/icons/icon-shop.tsx | 49 - .../src/components/icons/icon-sparkles.tsx | 54 - .../components/icons/icon-square-command.tsx | 31 - .../components/icons/icon-square-terminal.tsx | 33 - apps/web/src/components/icons/icon-star.tsx | 29 - apps/web/src/components/icons/icon-sun.tsx | 80 - .../web/src/components/icons/icon-support.tsx | 59 - apps/web/src/components/icons/icon-thread.tsx | 53 - apps/web/src/components/icons/icon-trash.tsx | 45 - apps/web/src/components/icons/icon-unpin.tsx | 47 - apps/web/src/components/icons/icon-user.tsx | 35 - .../src/components/icons/icon-users-plus.tsx | 55 - apps/web/src/components/icons/icon-users.tsx | 45 - .../src/components/icons/icon-volume-mute.tsx | 43 - apps/web/src/components/icons/icon-volume.tsx | 39 - .../web/src/components/icons/icon-warning.tsx | 41 - .../web/src/components/icons/icon-webhook.tsx | 84 - apps/web/src/components/icons/icon-xmark.tsx | 35 - .../add-github-subscription-modal.tsx | 408 --- .../add-rss-subscription-modal.tsx | 259 -- .../integrations/configure-maple-modal.tsx | 137 - .../configure-openstatus-modal.tsx | 146 -- .../integrations/configure-railway-modal.tsx | 146 -- .../edit-github-subscription-modal.tsx | 225 -- .../integrations/github-pr-embed.tsx | 347 --- .../github-subscriptions-section.tsx | 430 ---- apps/web/src/components/integrations/index.ts | 2 - .../integrations/linear-issue-embed.tsx | 230 -- .../maple-integration-content.tsx | 303 --- .../openstatus-integration-content.tsx | 321 --- .../railway-integration-content.tsx | 320 --- .../rss-subscriptions-section.tsx | 415 --- apps/web/src/components/link-preview.tsx | 51 - apps/web/src/components/loader.tsx | 30 - apps/web/src/components/logo.tsx | 21 - apps/web/src/components/mobile-nav.tsx | 118 - .../web/src/components/modals/appearances.tsx | 827 ------ .../components/modals/change-role-modal.tsx | 111 - .../components/modals/create-bot-modal.tsx | 274 -- .../modals/create-channel-modal.tsx | 187 -- .../src/components/modals/create-dm-modal.tsx | 303 --- .../modals/create-organization-modal.tsx | 162 -- .../modals/create-section-modal.tsx | 113 - .../modals/delete-channel-modal.tsx | 82 - .../modals/delete-workspace-modal.tsx | 116 - .../src/components/modals/edit-bot-modal.tsx | 258 -- .../components/modals/email-invite-modal.tsx | 207 -- .../src/components/modals/feedback-modal.tsx | 162 -- .../components/modals/join-channel-modal.tsx | 161 -- .../modals/rename-channel-modal.tsx | 132 - .../components/modals/rename-thread-modal.tsx | 132 - .../modals/request-integration-modal.tsx | 182 -- .../components/modals/set-status-modal.tsx | 340 --- .../notifications/notification-item.tsx | 145 -- .../notifications/notification-list.tsx | 61 - .../onboarding/invite-team-step.tsx | 180 -- .../onboarding/onboarding-layout.tsx | 93 - .../onboarding/onboarding-navigation.tsx | 45 - .../components/onboarding/org-setup-step.tsx | 72 - .../onboarding/profile-info-step.tsx | 136 - .../src/components/onboarding/role-step.tsx | 131 - .../onboarding/theme-selection-step.tsx | 183 -- .../onboarding/timezone-selection-step.tsx | 256 -- .../components/onboarding/use-case-step.tsx | 106 - .../components/onboarding/welcome-step.tsx | 80 - .../components/profile/avatar-crop-modal.tsx | 152 -- apps/web/src/components/profile/crop-area.tsx | 190 -- .../profile/profile-picture-upload.tsx | 244 -- apps/web/src/components/route-error.tsx | 87 - apps/web/src/components/route-not-found.tsx | 58 - .../src/components/sidebar/app-sidebar.tsx | 44 - .../src/components/sidebar/channel-item.tsx | 270 -- .../components/sidebar/channels-sidebar.tsx | 477 ---- .../sidebar/discoverable-channels.tsx | 126 - .../components/sidebar/dm-channel-item.tsx | 290 --- .../components/sidebar/favorite-section.tsx | 86 - .../sidebar/my-settings-sidebar.tsx | 210 -- .../src/components/sidebar/nav-sidebar.tsx | 146 -- .../sidebar/notifications-sidebar.tsx | 140 - .../src/components/sidebar/section-group.tsx | 218 -- .../components/sidebar/settings-sidebar.tsx | 315 --- .../components/sidebar/switch-server-menu.tsx | 73 - .../src/components/sidebar/thread-item.tsx | 112 - apps/web/src/components/sidebar/user-menu.tsx | 137 - .../components/status/user-status-badge.tsx | 155 -- .../src/components/tauri-menu-listener.tsx | 58 - .../web/src/components/tauri-update-check.tsx | 124 - apps/web/src/components/theme-provider.tsx | 219 -- .../components/theme/GrayPaletteSelector.tsx | 121 - .../src/components/theme/RadiusSelector.tsx | 119 - .../src/components/theme/ThemePresetCard.tsx | 125 - .../components/theme/ThemeRemixSection.tsx | 156 -- apps/web/src/components/theme/index.ts | 4 - apps/web/src/components/time-ribbon.tsx | 83 - apps/web/src/components/tweet-embed.tsx | 287 --- .../src/components/ui/avatar/avatar-badge.tsx | 11 - .../components/ui/avatar/avatar-context.tsx | 39 - .../components/ui/avatar/avatar-fallback.tsx | 68 - .../src/components/ui/avatar/avatar-image.tsx | 37 - .../src/components/ui/avatar/avatar-root.tsx | 59 - .../ui/avatar/avatar-status-indicator.tsx | 13 - .../ui/avatar/avatar-verified-badge.tsx | 21 - apps/web/src/components/ui/avatar/avatar.tsx | 130 - .../avatar-online-indicator.tsx | 35 - .../ui/avatar/base-components/index.tsx | 2 - .../avatar/base-components/verified-tick.tsx | 42 - apps/web/src/components/ui/avatar/index.ts | 10 - apps/web/src/components/ui/avatar/utils.ts | 12 - apps/web/src/components/ui/badge.tsx | 18 - apps/web/src/components/ui/button.tsx | 29 - apps/web/src/components/ui/calendar.tsx | 201 -- apps/web/src/components/ui/card.tsx | 52 - apps/web/src/components/ui/carousel.tsx | 238 -- apps/web/src/components/ui/checkbox.tsx | 70 - apps/web/src/components/ui/choice-box.tsx | 136 - apps/web/src/components/ui/combo-box.tsx | 97 - .../src/components/ui/command-menu-form.tsx | 198 -- apps/web/src/components/ui/command-menu.tsx | 232 -- apps/web/src/components/ui/context-menu.tsx | 137 - apps/web/src/components/ui/date-field.tsx | 36 - apps/web/src/components/ui/date-picker.tsx | 99 - apps/web/src/components/ui/dialog.tsx | 160 -- apps/web/src/components/ui/dropdown.tsx | 104 - apps/web/src/components/ui/empty-state.tsx | 26 - .../components/ui/field-validation-state.tsx | 84 - apps/web/src/components/ui/field.tsx | 77 - .../src/components/ui/form-error-summary.tsx | 60 - apps/web/src/components/ui/input-otp.tsx | 72 - apps/web/src/components/ui/input.tsx | 24 - apps/web/src/components/ui/keyboard.tsx | 14 - apps/web/src/components/ui/link.tsx | 18 - apps/web/src/components/ui/list-box.tsx | 105 - apps/web/src/components/ui/loader.tsx | 96 - apps/web/src/components/ui/menu.tsx | 178 -- apps/web/src/components/ui/modal.tsx | 106 - apps/web/src/components/ui/popover.tsx | 84 - apps/web/src/components/ui/progress-bar.tsx | 71 - apps/web/src/components/ui/radio.tsx | 39 - apps/web/src/components/ui/range-calendar.tsx | 68 - apps/web/src/components/ui/search-field.tsx | 33 - apps/web/src/components/ui/section-footer.tsx | 32 - apps/web/src/components/ui/section-header.tsx | 53 - apps/web/src/components/ui/section-label.tsx | 55 - apps/web/src/components/ui/select.tsx | 109 - apps/web/src/components/ui/separator.tsx | 16 - apps/web/src/components/ui/sheet.tsx | 108 - apps/web/src/components/ui/shiny-text.tsx | 123 - apps/web/src/components/ui/sidebar.tsx | 923 ------- apps/web/src/components/ui/slider.tsx | 72 - .../src/components/ui/split-panel/index.ts | 6 - .../ui/split-panel/split-panel-handle.tsx | 39 - .../components/ui/split-panel/split-panel.tsx | 167 -- .../ui/split-panel/use-panel-resize.ts | 228 -- apps/web/src/components/ui/switch.tsx | 38 - apps/web/src/components/ui/table.tsx | 252 -- apps/web/src/components/ui/tabs.tsx | 96 - apps/web/src/components/ui/text-field.tsx | 8 - apps/web/src/components/ui/text.tsx | 20 - apps/web/src/components/ui/textarea.tsx | 14 - apps/web/src/components/ui/time-field.tsx | 14 - .../web/src/components/ui/timezone-select.tsx | 39 - apps/web/src/components/ui/toast.tsx | 17 - apps/web/src/components/ui/toggle-group.tsx | 81 - apps/web/src/components/ui/toggle.tsx | 30 - apps/web/src/components/ui/toolbar.tsx | 12 - apps/web/src/components/ui/tooltip.tsx | 61 - apps/web/src/components/ui/tree.tsx | 79 - apps/web/src/components/version-check.tsx | 53 - apps/web/src/components/youtube-embed.tsx | 33 - apps/web/src/db/helper.ts | 0 apps/web/src/db/hooks.ts | 527 ---- apps/web/src/db/index.ts | 0 apps/web/src/db/materialized-collections.ts | 26 - apps/web/src/dev-gallery/entries/button.tsx | 76 - apps/web/src/dev-gallery/entries/calendar.tsx | 18 - apps/web/src/dev-gallery/entries/checkbox.tsx | 50 - .../src/dev-gallery/entries/choice-box.tsx | 62 - .../web/src/dev-gallery/entries/combo-box.tsx | 42 - .../dev-gallery/entries/command-menu-form.tsx | 68 - .../src/dev-gallery/entries/command-menu.tsx | 61 - apps/web/src/dev-gallery/entries/composer.tsx | 70 - .../src/dev-gallery/entries/context-menu.tsx | 41 - .../src/dev-gallery/entries/date-field.tsx | 35 - .../src/dev-gallery/entries/date-picker.tsx | 28 - apps/web/src/dev-gallery/entries/dialog.tsx | 59 - apps/web/src/dev-gallery/entries/dropdown.tsx | 54 - .../src/dev-gallery/entries/empty-state.tsx | 32 - .../entries/field-validation-state.tsx | 20 - apps/web/src/dev-gallery/entries/field.tsx | 41 - .../entries/form-error-summary.tsx | 32 - .../web/src/dev-gallery/entries/input-otp.tsx | 46 - apps/web/src/dev-gallery/entries/input.tsx | 54 - apps/web/src/dev-gallery/entries/link.tsx | 29 - apps/web/src/dev-gallery/entries/list-box.tsx | 67 - apps/web/src/dev-gallery/entries/loader.tsx | 19 - apps/web/src/dev-gallery/entries/menu.tsx | 100 - apps/web/src/dev-gallery/entries/modal.tsx | 50 - apps/web/src/dev-gallery/entries/popover.tsx | 47 - .../src/dev-gallery/entries/progress-bar.tsx | 48 - apps/web/src/dev-gallery/entries/radio.tsx | 47 - .../dev-gallery/entries/range-calendar.tsx | 16 - .../src/dev-gallery/entries/search-field.tsx | 38 - .../dev-gallery/entries/section-header.tsx | 61 - apps/web/src/dev-gallery/entries/select.tsx | 54 - .../web/src/dev-gallery/entries/separator.tsx | 25 - apps/web/src/dev-gallery/entries/sheet.tsx | 44 - apps/web/src/dev-gallery/entries/slider.tsx | 48 - apps/web/src/dev-gallery/entries/switch.tsx | 36 - .../dev-gallery/entries/table-selection.tsx | 39 - apps/web/src/dev-gallery/entries/table.tsx | 107 - apps/web/src/dev-gallery/entries/tabs.tsx | 71 - .../src/dev-gallery/entries/text-field.tsx | 57 - apps/web/src/dev-gallery/entries/text.tsx | 22 - apps/web/src/dev-gallery/entries/textarea.tsx | 43 - .../src/dev-gallery/entries/time-field.tsx | 28 - .../dev-gallery/entries/timezone-select.tsx | 15 - apps/web/src/dev-gallery/entries/toast.tsx | 62 - .../src/dev-gallery/entries/toggle-group.tsx | 68 - apps/web/src/dev-gallery/entries/toggle.tsx | 56 - apps/web/src/dev-gallery/entries/toolbar.tsx | 37 - apps/web/src/dev-gallery/entries/tooltip.tsx | 43 - apps/web/src/dev-gallery/entries/tree.tsx | 57 - apps/web/src/dev-gallery/frame.tsx | 21 - apps/web/src/hooks/message-actor-types.ts | 22 - apps/web/src/hooks/use-app-form.ts | 17 - apps/web/src/hooks/use-app-hotkey.ts | 25 - apps/web/src/hooks/use-bot-avatar-upload.tsx | 64 - apps/web/src/hooks/use-breakpoint.ts | 21 - .../src/hooks/use-channel-member-actions.ts | 88 - apps/web/src/hooks/use-chat.ts | 1 - apps/web/src/hooks/use-collection-error.ts | 183 -- apps/web/src/hooks/use-command-palette.ts | 202 -- apps/web/src/hooks/use-crop-interaction.ts | 158 -- .../src/hooks/use-debounced-intersection.ts | 66 - apps/web/src/hooks/use-drag-detection.ts | 54 - apps/web/src/hooks/use-embla-carousel-sync.ts | 114 - apps/web/src/hooks/use-emoji-stats.tsx | 40 - .../web/src/hooks/use-file-upload-handler.tsx | 104 - apps/web/src/hooks/use-file-upload.tsx | 195 -- .../src/hooks/use-global-keyboard-focus.ts | 81 - .../src/hooks/use-grouped-notifications.ts | 109 - .../src/hooks/use-intersection-observer.ts | 39 - apps/web/src/hooks/use-loading-state.ts | 90 - apps/web/src/hooks/use-media-query.ts | 16 - apps/web/src/hooks/use-message-actions.ts | 110 - apps/web/src/hooks/use-message-actor.test.ts | 22 - apps/web/src/hooks/use-message-actor.ts | 338 --- .../hooks/use-message-toolbar-overlay.test.ts | 127 - .../src/hooks/use-message-toolbar-overlay.ts | 30 - apps/web/src/hooks/use-mobile.ts | 19 - apps/web/src/hooks/use-modal-state.ts | 99 - apps/web/src/hooks/use-mount-effect.ts | 6 - apps/web/src/hooks/use-mounted.ts | 12 - .../src/hooks/use-notification-settings.ts | 107 - apps/web/src/hooks/use-notification-sound.tsx | 39 - apps/web/src/hooks/use-notifications.ts | 191 -- apps/web/src/hooks/use-onboarding.ts | 407 --- .../hooks/use-organization-avatar-upload.tsx | 71 - apps/web/src/hooks/use-organization.ts | 33 - apps/web/src/hooks/use-permission.ts | 68 - apps/web/src/hooks/use-posthog-identify.ts | 25 - apps/web/src/hooks/use-presence.ts | 649 ----- .../src/hooks/use-profile-picture-upload.tsx | 45 - apps/web/src/hooks/use-resize-observer.ts | 83 - .../hooks/use-scroll-into-view-on-active.ts | 25 - apps/web/src/hooks/use-scroll-restoration.ts | 52 - apps/web/src/hooks/use-search-query.ts | 338 --- apps/web/src/hooks/use-shared-channels.ts | 92 - apps/web/src/hooks/use-theme-settings.ts | 207 -- apps/web/src/hooks/use-typing-indicators.ts | 125 - apps/web/src/hooks/use-typing.test.tsx | 178 -- apps/web/src/hooks/use-typing.ts | 276 -- apps/web/src/hooks/use-upload.ts | 323 --- ...se-visible-message-notification-cleaner.ts | 158 -- apps/web/src/lib/auth.tsx | 41 - apps/web/src/lib/channels.ts | 88 - .../src/lib/helper/get-onboarding-image.ts | 35 - apps/web/src/lib/message-retry.ts | 12 - .../src/lib/notifications/selectors.test.ts | 47 - apps/web/src/lib/notifications/selectors.ts | 77 - apps/web/src/lib/primitive.ts | 28 - apps/web/src/lib/registry.ts | 9 - .../services/common/link-preview-client.ts | 10 - .../src/lib/services/common/network-mode.ts | 48 - apps/web/src/lib/toast-exit.tsx | 439 ---- .../typing-diagnostics/diagnostics-store.ts | 39 - apps/web/src/lib/typing-diagnostics/index.ts | 7 - apps/web/src/lib/typing-diagnostics/types.ts | 19 - apps/web/src/lib/version.ts | 32 - .../lib/web-callback-single-flight.test.ts | 75 - .../web/src/lib/web-callback-single-flight.ts | 81 - apps/web/src/main.tsx | 145 -- apps/web/src/providers/chat-provider.tsx | 724 ------ .../src/providers/message-hover-provider.tsx | 132 - .../providers/notification-sound-provider.tsx | 163 -- apps/web/src/providers/presence-provider.tsx | 11 - apps/web/src/reportWebVitals.ts | 13 - apps/web/src/routeTree.gen.ts | 1383 ---------- apps/web/src/routes/__root.tsx | 51 - .../channels/$channelId/settings/connect.tsx | 348 --- .../channels/$channelId/settings/index.tsx | 14 - .../$channelId/settings/integrations.tsx | 332 --- .../channels/$channelId/settings/layout.tsx | 140 - .../channels/$channelId/settings/overview.tsx | 187 -- .../web/src/routes/_app/$orgSlug/chat/$id.tsx | 173 -- .../_app/$orgSlug/chat/$id/files/index.tsx | 13 - .../_app/$orgSlug/chat/$id/files/media.tsx | 30 - .../routes/_app/$orgSlug/chat/$id/index.tsx | 59 - .../src/routes/_app/$orgSlug/chat/index.tsx | 391 --- apps/web/src/routes/_app/$orgSlug/index.tsx | 257 -- apps/web/src/routes/_app/$orgSlug/layout.tsx | 182 -- .../_app/$orgSlug/my-settings/desktop.tsx | 314 --- .../_app/$orgSlug/my-settings/index.tsx | 256 -- .../_app/$orgSlug/my-settings/layout.tsx | 15 - .../$orgSlug/my-settings/linked-accounts.tsx | 156 -- .../$orgSlug/my-settings/notifications.tsx | 243 -- .../_app/$orgSlug/my-settings/profile.tsx | 209 -- .../_app/$orgSlug/notifications/dms.tsx | 36 - .../_app/$orgSlug/notifications/general.tsx | 36 - .../_app/$orgSlug/notifications/index.tsx | 36 - .../_app/$orgSlug/notifications/layout.tsx | 15 - .../_app/$orgSlug/notifications/threads.tsx | 36 - .../routes/_app/$orgSlug/profile/$userId.tsx | 110 - .../settings/chat-sync/$connectionId.tsx | 832 ------ .../$orgSlug/settings/chat-sync/index.tsx | 364 --- .../$orgSlug/settings/chat-sync/layout.tsx | 13 - .../$orgSlug/settings/connect-invites.tsx | 248 -- .../_app/$orgSlug/settings/custom-emojis.tsx | 651 ----- .../routes/_app/$orgSlug/settings/debug.tsx | 489 ---- .../routes/_app/$orgSlug/settings/index.tsx | 372 --- .../settings/integrations/$integrationId.tsx | 969 ------- .../$orgSlug/settings/integrations/index.tsx | 247 -- .../settings/integrations/installed.tsx | 232 -- .../$orgSlug/settings/integrations/layout.tsx | 13 - .../settings/integrations/marketplace.tsx | 124 - .../settings/integrations/your-apps.tsx | 84 - .../_app/$orgSlug/settings/invitations.tsx | 201 -- .../routes/_app/$orgSlug/settings/layout.tsx | 15 - .../routes/_app/$orgSlug/settings/team.tsx | 355 --- apps/web/src/routes/_app/index.tsx | 67 - apps/web/src/routes/_app/layout.tsx | 102 - apps/web/src/routes/_app/onboarding/index.tsx | 269 -- .../_app/onboarding/setup-organization.tsx | 14 - .../routes/_app/select-organization/index.tsx | 153 -- apps/web/src/routes/_dev/layout.tsx | 29 - apps/web/src/routes/_dev/ui/agent-steps.tsx | 329 --- apps/web/src/routes/_dev/ui/layout.tsx | 32 - apps/web/src/routes/dev/embeds/demo.tsx | 297 --- apps/web/src/routes/dev/embeds/github.tsx | 160 -- apps/web/src/routes/dev/embeds/index.tsx | 54 - apps/web/src/routes/dev/embeds/openstatus.tsx | 87 - apps/web/src/routes/dev/embeds/railway.tsx | 116 - .../web/src/routes/dev/gallery/$component.tsx | 53 - apps/web/src/routes/join/$slug.tsx | 295 --- apps/web/src/routes/sign-in/$.tsx | 24 - apps/web/src/routes/sign-up/$.tsx | 24 - apps/web/src/utils/organization-navigation.ts | 29 - apps/web/src/vite-env.d.ts | 38 - apps/web/tsconfig.json | 34 - apps/web/vite.config.ts | 231 -- apps/web/vitest.config.ts | 15 - bun.lock | 2288 +++-------------- docs/foldkit-migration.md | 63 +- infra/README.md | 2 +- libs/tanstack-db-atom/README.md | 301 --- libs/tanstack-db-atom/package.json | 18 - .../src/AtomTanStackDB.result.test.ts | 561 ---- .../src/AtomTanStackDB.subscription.test.ts | 643 ----- .../src/AtomTanStackDB.test.ts | 674 ----- .../src/AtomTanStackDB.timing.test.ts | 393 --- libs/tanstack-db-atom/src/AtomTanStackDB.ts | 182 -- libs/tanstack-db-atom/src/index.ts | 17 - libs/tanstack-db-atom/src/types.ts | 79 - libs/tanstack-db-atom/tsconfig.json | 26 - package.json | 1 - packages/setup/src/commands/env.ts | 2 +- packages/setup/src/commands/setup.ts | 2 +- packages/setup/src/services/env-writer.ts | 2 +- packages/ui-parity/README.md | 29 +- packages/ui-parity/src/bench/render-trace.ts | 2 +- packages/ui-parity/src/bench/style-bisect.ts | 2 +- packages/ui-parity/src/config.ts | 4 +- packages/ui-parity/src/coverage.ts | 12 +- vitest.config.ts | 2 +- 810 files changed, 443 insertions(+), 76091 deletions(-) rename apps/{web => web-foldkit}/alchemy.run.ts (84%) rename apps/{web => web-foldkit}/public/apple-touch-icon-180x180.png (100%) rename apps/{web => web-foldkit}/public/brands/openstatus.svg (100%) rename apps/{web => web-foldkit}/public/favicon.ico (100%) rename apps/{web => web-foldkit}/public/icon.svg (100%) rename apps/{web => web-foldkit}/public/images/image-mask.webp (100%) rename apps/{web => web-foldkit}/public/images/masks/circle.webp (100%) rename apps/{web => web-foldkit}/public/images/masks/square.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/autumn-day.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/autumn-night.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/spring-day.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/spring-night.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/summer-day.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/summer-night.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/winter-day.webp (100%) rename apps/{web => web-foldkit}/public/images/onboarding/winter-night.webp (100%) rename apps/{web => web-foldkit}/public/images/squirrle_ocean.webp (100%) rename apps/{web => web-foldkit}/public/images/squirrle_window.webp (100%) rename apps/{web => web-foldkit}/public/manifest.json (100%) rename apps/{web => web-foldkit}/public/maskable-icon-512x512.png (100%) rename apps/{web => web-foldkit}/public/pwa-192x192.png (100%) rename apps/{web => web-foldkit}/public/pwa-512x512.png (100%) rename apps/{web => web-foldkit}/public/pwa-64x64.png (100%) rename apps/{web => web-foldkit}/public/robots.txt (100%) rename apps/{web => web-foldkit}/public/sounds/README.md (88%) rename apps/{web => web-foldkit}/public/sounds/keyboard/.gitkeep (100%) rename apps/{web => web-foldkit}/public/sounds/keyboard/README.md (100%) rename apps/{web => web-foldkit}/public/sounds/notification01.mp3 (100%) rename apps/{web => web-foldkit}/public/sounds/notification03.mp3 (100%) delete mode 100644 apps/web-foldkit/scripts/generate-appearance-illustrations.ts delete mode 100644 apps/web-foldkit/scripts/generate-file-icons.ts delete mode 100644 apps/web-foldkit/scripts/generate-icons.ts rename apps/{web/src => web-foldkit/src/core}/atoms/emoji-atoms.ts (100%) rename apps/{web/src => web-foldkit/src/core}/atoms/notification-sound-atoms.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/atoms/notification-sound-atoms.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/detect-language.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-code-decorator.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-emoji-decorator.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-markdown-decorations.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-markdown-serializer.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-markdown-serializer.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/slate-message-viewer-model.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/chat/slate-editor/types.ts (95%) rename apps/{web/src => web-foldkit/src/core}/components/embeds/embed-markdown.parser.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/embeds/embed-markdown.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/embeds/embed.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/link-preview.utils.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/avatar/styles.ts (92%) rename apps/{web/src => web-foldkit/src/core}/components/ui/badge.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/button.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/calendar.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/card.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/checkbox.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/choice-box.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/combo-box.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/command-menu-form.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/command-menu.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/context-menu.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/date-field.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/date-picker.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/dialog.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/dropdown.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/empty-state.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/field-validation-state.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/field.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/form-error-summary.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/input-otp.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/input.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/keyboard.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/link.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/list-box.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/loader.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/menu.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/modal.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/popover.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/progress-bar.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/radio.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/search-field.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/section-footer.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/section-header.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/section-label.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/select.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/separator.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/sheet.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/slider.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/sonner.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/split-panel/split-panel.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/switch.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/table.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/tabs.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/text.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/textarea.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/toast.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/toggle-group.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/toggle.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/toolbar.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/tooltip.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/components/ui/tree.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/db/actions.ts (99%) rename apps/{web/src => web-foldkit/src/core}/db/collections.ts (99%) rename apps/{web/src => web-foldkit/src/core}/dev-gallery/gallery.styles.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/access-token.test.ts (97%) rename apps/{web/src => web-foldkit/src/core}/lib/access-token.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/auth-fetch.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/bot-avatar.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/bot-scopes.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/connect-shared-channels.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/connect-shared-channels.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/custom-emoji-utils.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/date-fns-shared.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/electric-fetch.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/emoji-shortcode-map.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/emoji-utils.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/error-messages.ts (99%) rename apps/{web/src => web-foldkit/src/core}/lib/helper/generate-shades.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/hotkeys/hotkey-registry.test.ts (94%) rename apps/{web/src => web-foldkit/src/core}/lib/hotkeys/hotkey-registry.ts (97%) rename apps/{web/src => web-foldkit/src/core}/lib/integrations/__data.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/native-notifications.test.ts (98%) rename apps/{web/src => web-foldkit/src/core}/lib/native-notifications.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notification-sound-manager.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notification-sound-manager.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/README.md (91%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/diagnostics-store.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/index.ts (82%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/orchestrator.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/orchestrator.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/sinks/in-app.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/sinks/native.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/sinks/sound.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/notifications/types.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/platform-storage/index.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/platform-storage/platform-key-value-store.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/platform-storage/platform-runtime.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/platform-storage/tauri-key-value-store.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/rivet-client.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/rpc-auth-middleware.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/search-filter-parser.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/services/common/api-client.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/services/common/atom-client.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/services/common/rpc-atom-client.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/services/common/runtime.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/services/common/telemetry.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/session-bearer.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/tauri-autostart.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/tauri.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/theme/apply.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/theme/presets.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/theme/remix.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/upload-to-storage.ts (100%) rename apps/{web/src => web-foldkit/src/core}/lib/utils.ts (100%) rename apps/{web/src => web-foldkit/src/core}/styles/code-syntax.css (100%) rename apps/{web/src => web-foldkit/src/core}/styles/styles.css (99%) rename apps/{web/src => web-foldkit/src/core}/styles/theme.css (100%) rename apps/{web/src => web-foldkit/src/core}/utils/attachment-url.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/cx.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/file-utils.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/image-crop.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/presence.test.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/presence.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/status.ts (100%) rename apps/{web/src => web-foldkit/src/core}/utils/timezone.ts (100%) delete mode 100644 apps/web/.cta.json delete mode 100644 apps/web/.env.example delete mode 100644 apps/web/.gitignore delete mode 100644 apps/web/README.md delete mode 100644 apps/web/TODO.md delete mode 100644 apps/web/components.json delete mode 100644 apps/web/index.html delete mode 100644 apps/web/package.json delete mode 100644 apps/web/src/atoms/bot-atoms.ts delete mode 100644 apps/web/src/atoms/channel-atoms.ts delete mode 100644 apps/web/src/atoms/channel-member-atoms.ts delete mode 100644 apps/web/src/atoms/channel-webhook-atoms.ts delete mode 100644 apps/web/src/atoms/chat-atoms.ts delete mode 100644 apps/web/src/atoms/chat-query-atoms.ts delete mode 100644 apps/web/src/atoms/command-palette-state.ts delete mode 100644 apps/web/src/atoms/connect-share-atoms.ts delete mode 100644 apps/web/src/atoms/custom-emoji-atoms.ts delete mode 100644 apps/web/src/atoms/feature-discovery-atoms.ts delete mode 100644 apps/web/src/atoms/github-subscription-atoms.ts delete mode 100644 apps/web/src/atoms/hotkey-atoms.test.ts delete mode 100644 apps/web/src/atoms/hotkey-atoms.ts delete mode 100644 apps/web/src/atoms/loading-state-atoms.ts delete mode 100644 apps/web/src/atoms/message-atoms.ts delete mode 100644 apps/web/src/atoms/modal-atoms.ts delete mode 100644 apps/web/src/atoms/onboarding-atoms.ts delete mode 100644 apps/web/src/atoms/organization-atoms.ts delete mode 100644 apps/web/src/atoms/panel-atoms.ts delete mode 100644 apps/web/src/atoms/presence-atoms.ts delete mode 100644 apps/web/src/atoms/react-scan-atoms.ts delete mode 100644 apps/web/src/atoms/recent-channels-atom.ts delete mode 100644 apps/web/src/atoms/rss-subscription-atoms.ts delete mode 100644 apps/web/src/atoms/search-atoms.ts delete mode 100644 apps/web/src/atoms/section-collapse-atoms.ts delete mode 100644 apps/web/src/atoms/sidebar-atoms.ts delete mode 100644 apps/web/src/atoms/tauri-update-atoms.test.ts delete mode 100644 apps/web/src/atoms/tauri-update-atoms.ts delete mode 100644 apps/web/src/atoms/typing-indicator-atom.ts delete mode 100644 apps/web/src/atoms/user-atoms.ts delete mode 100644 apps/web/src/components/app-nav.tsx delete mode 100644 apps/web/src/components/bots/bot-avatar-upload.tsx delete mode 100644 apps/web/src/components/bots/bot-avatar.tsx delete mode 100644 apps/web/src/components/bots/bot-card.tsx delete mode 100644 apps/web/src/components/bots/bot-token-display.tsx delete mode 100644 apps/web/src/components/bots/marketplace-bot-card.tsx delete mode 100644 apps/web/src/components/channel-icon.tsx delete mode 100644 apps/web/src/components/channel-settings/add-github-repo-modal.tsx delete mode 100644 apps/web/src/components/channel-settings/add-rss-feed-modal.tsx delete mode 100644 apps/web/src/components/channel-settings/create-webhook-form.tsx delete mode 100644 apps/web/src/components/channel-settings/github-integration-card.tsx delete mode 100644 apps/web/src/components/channel-settings/github-subscription-card.tsx delete mode 100644 apps/web/src/components/channel-settings/integration-card.tsx delete mode 100644 apps/web/src/components/channel-settings/maple-section.tsx delete mode 100644 apps/web/src/components/channel-settings/openstatus-section.tsx delete mode 100644 apps/web/src/components/channel-settings/railway-section.tsx delete mode 100644 apps/web/src/components/channel-settings/rss-integration-card.tsx delete mode 100644 apps/web/src/components/channel-settings/token-display.tsx delete mode 100644 apps/web/src/components/chat-sync/add-channel-link-modal.tsx delete mode 100644 apps/web/src/components/chat-sync/add-connection-modal.tsx delete mode 100644 apps/web/src/components/chat/agent-steps-view.tsx delete mode 100644 apps/web/src/components/chat/author-identity.test.ts delete mode 100644 apps/web/src/components/chat/author-identity.ts delete mode 100644 apps/web/src/components/chat/channel-files/channel-files-documents-list.tsx delete mode 100644 apps/web/src/components/chat/channel-files/channel-files-header.tsx delete mode 100644 apps/web/src/components/chat/channel-files/channel-files-media-gallery-view.tsx delete mode 100644 apps/web/src/components/chat/channel-files/channel-files-media-grid.tsx delete mode 100644 apps/web/src/components/chat/channel-files/channel-files-view.tsx delete mode 100644 apps/web/src/components/chat/channel-files/index.ts delete mode 100644 apps/web/src/components/chat/channel-join-banner.tsx delete mode 100644 apps/web/src/components/chat/chat-header.tsx delete mode 100644 apps/web/src/components/chat/chat-tab-bar.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-actions.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-attachment-previews.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-context.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-drop-zone.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-edit-indicator.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-editor.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-frame.tsx delete mode 100644 apps/web/src/components/chat/composer/composer-reply-indicator.tsx delete mode 100644 apps/web/src/components/chat/composer/index.tsx delete mode 100644 apps/web/src/components/chat/delete-message-modal.tsx delete mode 100644 apps/web/src/components/chat/image-viewer-modal.tsx delete mode 100644 apps/web/src/components/chat/index.ts delete mode 100644 apps/web/src/components/chat/inline-thread-preview.tsx delete mode 100644 apps/web/src/components/chat/message-attachments.tsx delete mode 100644 apps/web/src/components/chat/message-composer-actions.tsx delete mode 100644 apps/web/src/components/chat/message-content.tsx delete mode 100644 apps/web/src/components/chat/message-context-menu.tsx delete mode 100644 apps/web/src/components/chat/message-embeds.tsx delete mode 100644 apps/web/src/components/chat/message-item.tsx delete mode 100644 apps/web/src/components/chat/message-list.tsx delete mode 100644 apps/web/src/components/chat/message-live-context.tsx delete mode 100644 apps/web/src/components/chat/message-live-state.test.tsx delete mode 100644 apps/web/src/components/chat/message-live-state.tsx delete mode 100644 apps/web/src/components/chat/message-reply-section.tsx delete mode 100644 apps/web/src/components/chat/message-toolbar.tsx delete mode 100644 apps/web/src/components/chat/message.tsx delete mode 100644 apps/web/src/components/chat/pinned-messages-modal.tsx delete mode 100644 apps/web/src/components/chat/reaction-button.tsx delete mode 100644 apps/web/src/components/chat/reply-indicator.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/autocomplete-listbox.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/command-input-panel.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/editor-autocomplete.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/hooks/use-bot-commands.ts delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/index.ts delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/slate-autocomplete-plugin.ts delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/triggers/bot-command-item.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/triggers/command-trigger.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/triggers/emoji-trigger.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/triggers/mention-trigger.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/types.ts delete mode 100644 apps/web/src/components/chat/slate-editor/autocomplete/use-slate-combobox.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/code-block-element.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/custom-emoji-element.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/heading-element.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/link-leaf.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/mention-element.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/mention-leaf.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/slate-code-block-paste-plugin.ts delete mode 100644 apps/web/src/components/chat/slate-editor/slate-file-paste-plugin.ts delete mode 100644 apps/web/src/components/chat/slate-editor/slate-markdown-decorators.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/slate-mention-plugin.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/slate-message-composer.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/slate-message-editor.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/slate-message-viewer.tsx delete mode 100644 apps/web/src/components/chat/slate-editor/table-element.tsx delete mode 100644 apps/web/src/components/chat/streaming-markdown.tsx delete mode 100644 apps/web/src/components/chat/thread-message-item.tsx delete mode 100644 apps/web/src/components/chat/thread-message-list.tsx delete mode 100644 apps/web/src/components/chat/thread-panel.tsx delete mode 100644 apps/web/src/components/chat/typing-indicator.tsx delete mode 100644 apps/web/src/components/chat/user-profile-popover.tsx delete mode 100644 apps/web/src/components/chat/video-player.tsx delete mode 100644 apps/web/src/components/city-card.tsx delete mode 100644 apps/web/src/components/command-palette.tsx delete mode 100644 apps/web/src/components/command-palette/command-palette-context.tsx delete mode 100644 apps/web/src/components/command-palette/command-palette-root.tsx delete mode 100644 apps/web/src/components/command-palette/markdown-text.tsx delete mode 100644 apps/web/src/components/command-palette/pages/appearance-view.tsx delete mode 100644 apps/web/src/components/command-palette/pages/create-channel-view.tsx delete mode 100644 apps/web/src/components/command-palette/pages/home-view.tsx delete mode 100644 apps/web/src/components/command-palette/pages/index.ts delete mode 100644 apps/web/src/components/command-palette/pages/join-channel-view.tsx delete mode 100644 apps/web/src/components/command-palette/pages/status-view.tsx delete mode 100644 apps/web/src/components/command-palette/search-filter-chip.tsx delete mode 100644 apps/web/src/components/command-palette/search-filter-decorator.tsx delete mode 100644 apps/web/src/components/command-palette/search-result-item.tsx delete mode 100644 apps/web/src/components/command-palette/search-slate-editor.tsx delete mode 100644 apps/web/src/components/command-palette/search-view.tsx delete mode 100644 apps/web/src/components/connect/share-channel-modal.test.tsx delete mode 100644 apps/web/src/components/connect/share-channel-modal.tsx delete mode 100644 apps/web/src/components/embeds/embed-author.tsx delete mode 100644 apps/web/src/components/embeds/embed-body.tsx delete mode 100644 apps/web/src/components/embeds/embed-connect-prompt.tsx delete mode 100644 apps/web/src/components/embeds/embed-error.tsx delete mode 100644 apps/web/src/components/embeds/embed-fields.tsx delete mode 100644 apps/web/src/components/embeds/embed-footer.tsx delete mode 100644 apps/web/src/components/embeds/embed-image.tsx delete mode 100644 apps/web/src/components/embeds/embed-markdown.tsx delete mode 100644 apps/web/src/components/embeds/embed-skeleton.tsx delete mode 100644 apps/web/src/components/embeds/embed-thumbnail.tsx delete mode 100644 apps/web/src/components/embeds/embed.tsx delete mode 100644 apps/web/src/components/embeds/index.ts delete mode 100644 apps/web/src/components/embeds/use-embed-theme.ts delete mode 100644 apps/web/src/components/emoji-picker/custom-emoji-section.tsx delete mode 100644 apps/web/src/components/emoji-picker/emoji-picker-content.tsx delete mode 100644 apps/web/src/components/emoji-picker/emoji-picker-dialog.tsx delete mode 100644 apps/web/src/components/emoji-picker/emoji-picker-footer.tsx delete mode 100644 apps/web/src/components/emoji-picker/emoji-picker-search.tsx delete mode 100644 apps/web/src/components/emoji-picker/emoji-picker.tsx delete mode 100644 apps/web/src/components/emoji-picker/index.ts delete mode 100644 apps/web/src/components/emoji-preview.tsx delete mode 100644 apps/web/src/components/gif-embed.tsx delete mode 100644 apps/web/src/components/gif-picker/gif-picker-attribution.tsx delete mode 100644 apps/web/src/components/gif-picker/gif-picker-categories.tsx delete mode 100644 apps/web/src/components/gif-picker/gif-picker-dialog.tsx delete mode 100644 apps/web/src/components/gif-picker/gif-picker-grid.tsx delete mode 100644 apps/web/src/components/gif-picker/gif-picker-search.tsx delete mode 100644 apps/web/src/components/gif-picker/index.ts delete mode 100644 apps/web/src/components/gif-picker/use-klipy.ts delete mode 100644 apps/web/src/components/globe-visual.tsx delete mode 100644 apps/web/src/components/icons/icon-arrow-left.tsx delete mode 100644 apps/web/src/components/icons/icon-arrow-path.tsx delete mode 100644 apps/web/src/components/icons/icon-bell-slash.tsx delete mode 100644 apps/web/src/components/icons/icon-bell.tsx delete mode 100644 apps/web/src/components/icons/icon-brain-sparkle.tsx delete mode 100644 apps/web/src/components/icons/icon-branch.tsx delete mode 100644 apps/web/src/components/icons/icon-changelog.tsx delete mode 100644 apps/web/src/components/icons/icon-chart-bar.tsx delete mode 100644 apps/web/src/components/icons/icon-chat-bubble.tsx delete mode 100644 apps/web/src/components/icons/icon-check.tsx delete mode 100644 apps/web/src/components/icons/icon-chevron-down.tsx delete mode 100644 apps/web/src/components/icons/icon-chevron-left.tsx delete mode 100644 apps/web/src/components/icons/icon-chevron-right.tsx delete mode 100644 apps/web/src/components/icons/icon-chevron-up-down.tsx delete mode 100644 apps/web/src/components/icons/icon-chevron-up.tsx delete mode 100644 apps/web/src/components/icons/icon-circle-dotted-user.tsx delete mode 100644 apps/web/src/components/icons/icon-circle-pause.tsx delete mode 100644 apps/web/src/components/icons/icon-circle-plus.tsx delete mode 100644 apps/web/src/components/icons/icon-clock.tsx delete mode 100644 apps/web/src/components/icons/icon-close.tsx delete mode 100644 apps/web/src/components/icons/icon-code.tsx delete mode 100644 apps/web/src/components/icons/icon-company.tsx delete mode 100644 apps/web/src/components/icons/icon-connect.tsx delete mode 100644 apps/web/src/components/icons/icon-copy.tsx delete mode 100644 apps/web/src/components/icons/icon-cube.tsx delete mode 100644 apps/web/src/components/icons/icon-dashboard.tsx delete mode 100644 apps/web/src/components/icons/icon-discord.tsx delete mode 100644 apps/web/src/components/icons/icon-dots-vertical.tsx delete mode 100644 apps/web/src/components/icons/icon-dots.tsx delete mode 100644 apps/web/src/components/icons/icon-download.tsx delete mode 100644 apps/web/src/components/icons/icon-edit.tsx delete mode 100644 apps/web/src/components/icons/icon-emoji-1.tsx delete mode 100644 apps/web/src/components/icons/icon-emoji-add.tsx delete mode 100644 apps/web/src/components/icons/icon-envelope.tsx delete mode 100644 apps/web/src/components/icons/icon-eye-slash.tsx delete mode 100644 apps/web/src/components/icons/icon-eye.tsx delete mode 100644 apps/web/src/components/icons/icon-folder-plus.tsx delete mode 100644 apps/web/src/components/icons/icon-folder.tsx delete mode 100644 apps/web/src/components/icons/icon-gear.tsx delete mode 100644 apps/web/src/components/icons/icon-gif.tsx delete mode 100644 apps/web/src/components/icons/icon-grid-circle-plus.tsx delete mode 100644 apps/web/src/components/icons/icon-hashtag.tsx delete mode 100644 apps/web/src/components/icons/icon-heart.tsx delete mode 100644 apps/web/src/components/icons/icon-integratio-.tsx delete mode 100644 apps/web/src/components/icons/icon-leave.tsx delete mode 100644 apps/web/src/components/icons/icon-lightbulb.tsx delete mode 100644 apps/web/src/components/icons/icon-link-external.tsx delete mode 100644 apps/web/src/components/icons/icon-loader.tsx delete mode 100644 apps/web/src/components/icons/icon-lock.tsx delete mode 100644 apps/web/src/components/icons/icon-logout.tsx delete mode 100644 apps/web/src/components/icons/icon-magic-wand-sparkle.tsx delete mode 100644 apps/web/src/components/icons/icon-magnifier-3.tsx delete mode 100644 apps/web/src/components/icons/icon-map-pin.tsx delete mode 100644 apps/web/src/components/icons/icon-megaphone.tsx delete mode 100644 apps/web/src/components/icons/icon-menu.tsx delete mode 100644 apps/web/src/components/icons/icon-minus.tsx delete mode 100644 apps/web/src/components/icons/icon-moon.tsx delete mode 100644 apps/web/src/components/icons/icon-msg-clock.tsx delete mode 100644 apps/web/src/components/icons/icon-msgs.tsx delete mode 100644 apps/web/src/components/icons/icon-paintbrush.tsx delete mode 100644 apps/web/src/components/icons/icon-paperclip2.tsx delete mode 100644 apps/web/src/components/icons/icon-pen-sparkle.tsx delete mode 100644 apps/web/src/components/icons/icon-persons-2.tsx delete mode 100644 apps/web/src/components/icons/icon-phone.tsx delete mode 100644 apps/web/src/components/icons/icon-pin.tsx delete mode 100644 apps/web/src/components/icons/icon-play.tsx delete mode 100644 apps/web/src/components/icons/icon-plus.tsx delete mode 100644 apps/web/src/components/icons/icon-presentation-chart.tsx delete mode 100644 apps/web/src/components/icons/icon-reply.tsx delete mode 100644 apps/web/src/components/icons/icon-robot.tsx delete mode 100644 apps/web/src/components/icons/icon-rocket.tsx delete mode 100644 apps/web/src/components/icons/icon-servers.tsx delete mode 100644 apps/web/src/components/icons/icon-share.tsx delete mode 100644 apps/web/src/components/icons/icon-shop.tsx delete mode 100644 apps/web/src/components/icons/icon-sparkles.tsx delete mode 100644 apps/web/src/components/icons/icon-square-command.tsx delete mode 100644 apps/web/src/components/icons/icon-square-terminal.tsx delete mode 100644 apps/web/src/components/icons/icon-star.tsx delete mode 100644 apps/web/src/components/icons/icon-sun.tsx delete mode 100644 apps/web/src/components/icons/icon-support.tsx delete mode 100644 apps/web/src/components/icons/icon-thread.tsx delete mode 100644 apps/web/src/components/icons/icon-trash.tsx delete mode 100644 apps/web/src/components/icons/icon-unpin.tsx delete mode 100644 apps/web/src/components/icons/icon-user.tsx delete mode 100644 apps/web/src/components/icons/icon-users-plus.tsx delete mode 100644 apps/web/src/components/icons/icon-users.tsx delete mode 100644 apps/web/src/components/icons/icon-volume-mute.tsx delete mode 100644 apps/web/src/components/icons/icon-volume.tsx delete mode 100644 apps/web/src/components/icons/icon-warning.tsx delete mode 100644 apps/web/src/components/icons/icon-webhook.tsx delete mode 100644 apps/web/src/components/icons/icon-xmark.tsx delete mode 100644 apps/web/src/components/integrations/add-github-subscription-modal.tsx delete mode 100644 apps/web/src/components/integrations/add-rss-subscription-modal.tsx delete mode 100644 apps/web/src/components/integrations/configure-maple-modal.tsx delete mode 100644 apps/web/src/components/integrations/configure-openstatus-modal.tsx delete mode 100644 apps/web/src/components/integrations/configure-railway-modal.tsx delete mode 100644 apps/web/src/components/integrations/edit-github-subscription-modal.tsx delete mode 100644 apps/web/src/components/integrations/github-pr-embed.tsx delete mode 100644 apps/web/src/components/integrations/github-subscriptions-section.tsx delete mode 100644 apps/web/src/components/integrations/index.ts delete mode 100644 apps/web/src/components/integrations/linear-issue-embed.tsx delete mode 100644 apps/web/src/components/integrations/maple-integration-content.tsx delete mode 100644 apps/web/src/components/integrations/openstatus-integration-content.tsx delete mode 100644 apps/web/src/components/integrations/railway-integration-content.tsx delete mode 100644 apps/web/src/components/integrations/rss-subscriptions-section.tsx delete mode 100644 apps/web/src/components/link-preview.tsx delete mode 100644 apps/web/src/components/loader.tsx delete mode 100644 apps/web/src/components/logo.tsx delete mode 100644 apps/web/src/components/mobile-nav.tsx delete mode 100644 apps/web/src/components/modals/appearances.tsx delete mode 100644 apps/web/src/components/modals/change-role-modal.tsx delete mode 100644 apps/web/src/components/modals/create-bot-modal.tsx delete mode 100644 apps/web/src/components/modals/create-channel-modal.tsx delete mode 100644 apps/web/src/components/modals/create-dm-modal.tsx delete mode 100644 apps/web/src/components/modals/create-organization-modal.tsx delete mode 100644 apps/web/src/components/modals/create-section-modal.tsx delete mode 100644 apps/web/src/components/modals/delete-channel-modal.tsx delete mode 100644 apps/web/src/components/modals/delete-workspace-modal.tsx delete mode 100644 apps/web/src/components/modals/edit-bot-modal.tsx delete mode 100644 apps/web/src/components/modals/email-invite-modal.tsx delete mode 100644 apps/web/src/components/modals/feedback-modal.tsx delete mode 100644 apps/web/src/components/modals/join-channel-modal.tsx delete mode 100644 apps/web/src/components/modals/rename-channel-modal.tsx delete mode 100644 apps/web/src/components/modals/rename-thread-modal.tsx delete mode 100644 apps/web/src/components/modals/request-integration-modal.tsx delete mode 100644 apps/web/src/components/modals/set-status-modal.tsx delete mode 100644 apps/web/src/components/notifications/notification-item.tsx delete mode 100644 apps/web/src/components/notifications/notification-list.tsx delete mode 100644 apps/web/src/components/onboarding/invite-team-step.tsx delete mode 100644 apps/web/src/components/onboarding/onboarding-layout.tsx delete mode 100644 apps/web/src/components/onboarding/onboarding-navigation.tsx delete mode 100644 apps/web/src/components/onboarding/org-setup-step.tsx delete mode 100644 apps/web/src/components/onboarding/profile-info-step.tsx delete mode 100644 apps/web/src/components/onboarding/role-step.tsx delete mode 100644 apps/web/src/components/onboarding/theme-selection-step.tsx delete mode 100644 apps/web/src/components/onboarding/timezone-selection-step.tsx delete mode 100644 apps/web/src/components/onboarding/use-case-step.tsx delete mode 100644 apps/web/src/components/onboarding/welcome-step.tsx delete mode 100644 apps/web/src/components/profile/avatar-crop-modal.tsx delete mode 100644 apps/web/src/components/profile/crop-area.tsx delete mode 100644 apps/web/src/components/profile/profile-picture-upload.tsx delete mode 100644 apps/web/src/components/route-error.tsx delete mode 100644 apps/web/src/components/route-not-found.tsx delete mode 100644 apps/web/src/components/sidebar/app-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/channel-item.tsx delete mode 100644 apps/web/src/components/sidebar/channels-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/discoverable-channels.tsx delete mode 100644 apps/web/src/components/sidebar/dm-channel-item.tsx delete mode 100644 apps/web/src/components/sidebar/favorite-section.tsx delete mode 100644 apps/web/src/components/sidebar/my-settings-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/nav-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/notifications-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/section-group.tsx delete mode 100644 apps/web/src/components/sidebar/settings-sidebar.tsx delete mode 100644 apps/web/src/components/sidebar/switch-server-menu.tsx delete mode 100644 apps/web/src/components/sidebar/thread-item.tsx delete mode 100644 apps/web/src/components/sidebar/user-menu.tsx delete mode 100644 apps/web/src/components/status/user-status-badge.tsx delete mode 100644 apps/web/src/components/tauri-menu-listener.tsx delete mode 100644 apps/web/src/components/tauri-update-check.tsx delete mode 100644 apps/web/src/components/theme-provider.tsx delete mode 100644 apps/web/src/components/theme/GrayPaletteSelector.tsx delete mode 100644 apps/web/src/components/theme/RadiusSelector.tsx delete mode 100644 apps/web/src/components/theme/ThemePresetCard.tsx delete mode 100644 apps/web/src/components/theme/ThemeRemixSection.tsx delete mode 100644 apps/web/src/components/theme/index.ts delete mode 100644 apps/web/src/components/time-ribbon.tsx delete mode 100644 apps/web/src/components/tweet-embed.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-badge.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-context.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-fallback.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-image.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-root.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-status-indicator.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar-verified-badge.tsx delete mode 100644 apps/web/src/components/ui/avatar/avatar.tsx delete mode 100644 apps/web/src/components/ui/avatar/base-components/avatar-online-indicator.tsx delete mode 100644 apps/web/src/components/ui/avatar/base-components/index.tsx delete mode 100644 apps/web/src/components/ui/avatar/base-components/verified-tick.tsx delete mode 100644 apps/web/src/components/ui/avatar/index.ts delete mode 100644 apps/web/src/components/ui/avatar/utils.ts delete mode 100644 apps/web/src/components/ui/badge.tsx delete mode 100644 apps/web/src/components/ui/button.tsx delete mode 100644 apps/web/src/components/ui/calendar.tsx delete mode 100644 apps/web/src/components/ui/card.tsx delete mode 100644 apps/web/src/components/ui/carousel.tsx delete mode 100644 apps/web/src/components/ui/checkbox.tsx delete mode 100644 apps/web/src/components/ui/choice-box.tsx delete mode 100644 apps/web/src/components/ui/combo-box.tsx delete mode 100644 apps/web/src/components/ui/command-menu-form.tsx delete mode 100644 apps/web/src/components/ui/command-menu.tsx delete mode 100644 apps/web/src/components/ui/context-menu.tsx delete mode 100644 apps/web/src/components/ui/date-field.tsx delete mode 100644 apps/web/src/components/ui/date-picker.tsx delete mode 100644 apps/web/src/components/ui/dialog.tsx delete mode 100644 apps/web/src/components/ui/dropdown.tsx delete mode 100644 apps/web/src/components/ui/empty-state.tsx delete mode 100644 apps/web/src/components/ui/field-validation-state.tsx delete mode 100644 apps/web/src/components/ui/field.tsx delete mode 100644 apps/web/src/components/ui/form-error-summary.tsx delete mode 100644 apps/web/src/components/ui/input-otp.tsx delete mode 100644 apps/web/src/components/ui/input.tsx delete mode 100644 apps/web/src/components/ui/keyboard.tsx delete mode 100644 apps/web/src/components/ui/link.tsx delete mode 100644 apps/web/src/components/ui/list-box.tsx delete mode 100644 apps/web/src/components/ui/loader.tsx delete mode 100644 apps/web/src/components/ui/menu.tsx delete mode 100644 apps/web/src/components/ui/modal.tsx delete mode 100644 apps/web/src/components/ui/popover.tsx delete mode 100644 apps/web/src/components/ui/progress-bar.tsx delete mode 100644 apps/web/src/components/ui/radio.tsx delete mode 100644 apps/web/src/components/ui/range-calendar.tsx delete mode 100644 apps/web/src/components/ui/search-field.tsx delete mode 100644 apps/web/src/components/ui/section-footer.tsx delete mode 100644 apps/web/src/components/ui/section-header.tsx delete mode 100644 apps/web/src/components/ui/section-label.tsx delete mode 100644 apps/web/src/components/ui/select.tsx delete mode 100644 apps/web/src/components/ui/separator.tsx delete mode 100644 apps/web/src/components/ui/sheet.tsx delete mode 100644 apps/web/src/components/ui/shiny-text.tsx delete mode 100644 apps/web/src/components/ui/sidebar.tsx delete mode 100644 apps/web/src/components/ui/slider.tsx delete mode 100644 apps/web/src/components/ui/split-panel/index.ts delete mode 100644 apps/web/src/components/ui/split-panel/split-panel-handle.tsx delete mode 100644 apps/web/src/components/ui/split-panel/split-panel.tsx delete mode 100644 apps/web/src/components/ui/split-panel/use-panel-resize.ts delete mode 100644 apps/web/src/components/ui/switch.tsx delete mode 100644 apps/web/src/components/ui/table.tsx delete mode 100644 apps/web/src/components/ui/tabs.tsx delete mode 100644 apps/web/src/components/ui/text-field.tsx delete mode 100644 apps/web/src/components/ui/text.tsx delete mode 100644 apps/web/src/components/ui/textarea.tsx delete mode 100644 apps/web/src/components/ui/time-field.tsx delete mode 100644 apps/web/src/components/ui/timezone-select.tsx delete mode 100644 apps/web/src/components/ui/toast.tsx delete mode 100644 apps/web/src/components/ui/toggle-group.tsx delete mode 100644 apps/web/src/components/ui/toggle.tsx delete mode 100644 apps/web/src/components/ui/toolbar.tsx delete mode 100644 apps/web/src/components/ui/tooltip.tsx delete mode 100644 apps/web/src/components/ui/tree.tsx delete mode 100644 apps/web/src/components/version-check.tsx delete mode 100644 apps/web/src/components/youtube-embed.tsx delete mode 100644 apps/web/src/db/helper.ts delete mode 100644 apps/web/src/db/hooks.ts delete mode 100644 apps/web/src/db/index.ts delete mode 100644 apps/web/src/db/materialized-collections.ts delete mode 100644 apps/web/src/dev-gallery/entries/button.tsx delete mode 100644 apps/web/src/dev-gallery/entries/calendar.tsx delete mode 100644 apps/web/src/dev-gallery/entries/checkbox.tsx delete mode 100644 apps/web/src/dev-gallery/entries/choice-box.tsx delete mode 100644 apps/web/src/dev-gallery/entries/combo-box.tsx delete mode 100644 apps/web/src/dev-gallery/entries/command-menu-form.tsx delete mode 100644 apps/web/src/dev-gallery/entries/command-menu.tsx delete mode 100644 apps/web/src/dev-gallery/entries/composer.tsx delete mode 100644 apps/web/src/dev-gallery/entries/context-menu.tsx delete mode 100644 apps/web/src/dev-gallery/entries/date-field.tsx delete mode 100644 apps/web/src/dev-gallery/entries/date-picker.tsx delete mode 100644 apps/web/src/dev-gallery/entries/dialog.tsx delete mode 100644 apps/web/src/dev-gallery/entries/dropdown.tsx delete mode 100644 apps/web/src/dev-gallery/entries/empty-state.tsx delete mode 100644 apps/web/src/dev-gallery/entries/field-validation-state.tsx delete mode 100644 apps/web/src/dev-gallery/entries/field.tsx delete mode 100644 apps/web/src/dev-gallery/entries/form-error-summary.tsx delete mode 100644 apps/web/src/dev-gallery/entries/input-otp.tsx delete mode 100644 apps/web/src/dev-gallery/entries/input.tsx delete mode 100644 apps/web/src/dev-gallery/entries/link.tsx delete mode 100644 apps/web/src/dev-gallery/entries/list-box.tsx delete mode 100644 apps/web/src/dev-gallery/entries/loader.tsx delete mode 100644 apps/web/src/dev-gallery/entries/menu.tsx delete mode 100644 apps/web/src/dev-gallery/entries/modal.tsx delete mode 100644 apps/web/src/dev-gallery/entries/popover.tsx delete mode 100644 apps/web/src/dev-gallery/entries/progress-bar.tsx delete mode 100644 apps/web/src/dev-gallery/entries/radio.tsx delete mode 100644 apps/web/src/dev-gallery/entries/range-calendar.tsx delete mode 100644 apps/web/src/dev-gallery/entries/search-field.tsx delete mode 100644 apps/web/src/dev-gallery/entries/section-header.tsx delete mode 100644 apps/web/src/dev-gallery/entries/select.tsx delete mode 100644 apps/web/src/dev-gallery/entries/separator.tsx delete mode 100644 apps/web/src/dev-gallery/entries/sheet.tsx delete mode 100644 apps/web/src/dev-gallery/entries/slider.tsx delete mode 100644 apps/web/src/dev-gallery/entries/switch.tsx delete mode 100644 apps/web/src/dev-gallery/entries/table-selection.tsx delete mode 100644 apps/web/src/dev-gallery/entries/table.tsx delete mode 100644 apps/web/src/dev-gallery/entries/tabs.tsx delete mode 100644 apps/web/src/dev-gallery/entries/text-field.tsx delete mode 100644 apps/web/src/dev-gallery/entries/text.tsx delete mode 100644 apps/web/src/dev-gallery/entries/textarea.tsx delete mode 100644 apps/web/src/dev-gallery/entries/time-field.tsx delete mode 100644 apps/web/src/dev-gallery/entries/timezone-select.tsx delete mode 100644 apps/web/src/dev-gallery/entries/toast.tsx delete mode 100644 apps/web/src/dev-gallery/entries/toggle-group.tsx delete mode 100644 apps/web/src/dev-gallery/entries/toggle.tsx delete mode 100644 apps/web/src/dev-gallery/entries/toolbar.tsx delete mode 100644 apps/web/src/dev-gallery/entries/tooltip.tsx delete mode 100644 apps/web/src/dev-gallery/entries/tree.tsx delete mode 100644 apps/web/src/dev-gallery/frame.tsx delete mode 100644 apps/web/src/hooks/message-actor-types.ts delete mode 100644 apps/web/src/hooks/use-app-form.ts delete mode 100644 apps/web/src/hooks/use-app-hotkey.ts delete mode 100644 apps/web/src/hooks/use-bot-avatar-upload.tsx delete mode 100644 apps/web/src/hooks/use-breakpoint.ts delete mode 100644 apps/web/src/hooks/use-channel-member-actions.ts delete mode 100644 apps/web/src/hooks/use-chat.ts delete mode 100644 apps/web/src/hooks/use-collection-error.ts delete mode 100644 apps/web/src/hooks/use-command-palette.ts delete mode 100644 apps/web/src/hooks/use-crop-interaction.ts delete mode 100644 apps/web/src/hooks/use-debounced-intersection.ts delete mode 100644 apps/web/src/hooks/use-drag-detection.ts delete mode 100644 apps/web/src/hooks/use-embla-carousel-sync.ts delete mode 100644 apps/web/src/hooks/use-emoji-stats.tsx delete mode 100644 apps/web/src/hooks/use-file-upload-handler.tsx delete mode 100644 apps/web/src/hooks/use-file-upload.tsx delete mode 100644 apps/web/src/hooks/use-global-keyboard-focus.ts delete mode 100644 apps/web/src/hooks/use-grouped-notifications.ts delete mode 100644 apps/web/src/hooks/use-intersection-observer.ts delete mode 100644 apps/web/src/hooks/use-loading-state.ts delete mode 100644 apps/web/src/hooks/use-media-query.ts delete mode 100644 apps/web/src/hooks/use-message-actions.ts delete mode 100644 apps/web/src/hooks/use-message-actor.test.ts delete mode 100644 apps/web/src/hooks/use-message-actor.ts delete mode 100644 apps/web/src/hooks/use-message-toolbar-overlay.test.ts delete mode 100644 apps/web/src/hooks/use-message-toolbar-overlay.ts delete mode 100644 apps/web/src/hooks/use-mobile.ts delete mode 100644 apps/web/src/hooks/use-modal-state.ts delete mode 100644 apps/web/src/hooks/use-mount-effect.ts delete mode 100644 apps/web/src/hooks/use-mounted.ts delete mode 100644 apps/web/src/hooks/use-notification-settings.ts delete mode 100644 apps/web/src/hooks/use-notification-sound.tsx delete mode 100644 apps/web/src/hooks/use-notifications.ts delete mode 100644 apps/web/src/hooks/use-onboarding.ts delete mode 100644 apps/web/src/hooks/use-organization-avatar-upload.tsx delete mode 100644 apps/web/src/hooks/use-organization.ts delete mode 100644 apps/web/src/hooks/use-permission.ts delete mode 100644 apps/web/src/hooks/use-posthog-identify.ts delete mode 100644 apps/web/src/hooks/use-presence.ts delete mode 100644 apps/web/src/hooks/use-profile-picture-upload.tsx delete mode 100644 apps/web/src/hooks/use-resize-observer.ts delete mode 100644 apps/web/src/hooks/use-scroll-into-view-on-active.ts delete mode 100644 apps/web/src/hooks/use-scroll-restoration.ts delete mode 100644 apps/web/src/hooks/use-search-query.ts delete mode 100644 apps/web/src/hooks/use-shared-channels.ts delete mode 100644 apps/web/src/hooks/use-theme-settings.ts delete mode 100644 apps/web/src/hooks/use-typing-indicators.ts delete mode 100644 apps/web/src/hooks/use-typing.test.tsx delete mode 100644 apps/web/src/hooks/use-typing.ts delete mode 100644 apps/web/src/hooks/use-upload.ts delete mode 100644 apps/web/src/hooks/use-visible-message-notification-cleaner.ts delete mode 100644 apps/web/src/lib/auth.tsx delete mode 100644 apps/web/src/lib/channels.ts delete mode 100644 apps/web/src/lib/helper/get-onboarding-image.ts delete mode 100644 apps/web/src/lib/message-retry.ts delete mode 100644 apps/web/src/lib/notifications/selectors.test.ts delete mode 100644 apps/web/src/lib/notifications/selectors.ts delete mode 100644 apps/web/src/lib/primitive.ts delete mode 100644 apps/web/src/lib/registry.ts delete mode 100644 apps/web/src/lib/services/common/link-preview-client.ts delete mode 100644 apps/web/src/lib/services/common/network-mode.ts delete mode 100644 apps/web/src/lib/toast-exit.tsx delete mode 100644 apps/web/src/lib/typing-diagnostics/diagnostics-store.ts delete mode 100644 apps/web/src/lib/typing-diagnostics/index.ts delete mode 100644 apps/web/src/lib/typing-diagnostics/types.ts delete mode 100644 apps/web/src/lib/version.ts delete mode 100644 apps/web/src/lib/web-callback-single-flight.test.ts delete mode 100644 apps/web/src/lib/web-callback-single-flight.ts delete mode 100644 apps/web/src/main.tsx delete mode 100644 apps/web/src/providers/chat-provider.tsx delete mode 100644 apps/web/src/providers/message-hover-provider.tsx delete mode 100644 apps/web/src/providers/notification-sound-provider.tsx delete mode 100644 apps/web/src/providers/presence-provider.tsx delete mode 100644 apps/web/src/reportWebVitals.ts delete mode 100644 apps/web/src/routeTree.gen.ts delete mode 100644 apps/web/src/routes/__root.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/channels/$channelId/settings/connect.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/channels/$channelId/settings/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/channels/$channelId/settings/integrations.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/channels/$channelId/settings/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/channels/$channelId/settings/overview.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/chat/$id.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/chat/$id/files/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/chat/$id/files/media.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/chat/$id/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/chat/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/desktop.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/linked-accounts.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/notifications.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/my-settings/profile.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/notifications/dms.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/notifications/general.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/notifications/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/notifications/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/notifications/threads.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/profile/$userId.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/chat-sync/$connectionId.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/chat-sync/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/chat-sync/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/connect-invites.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/custom-emojis.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/debug.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/$integrationId.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/index.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/installed.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/marketplace.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/integrations/your-apps.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/invitations.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/layout.tsx delete mode 100644 apps/web/src/routes/_app/$orgSlug/settings/team.tsx delete mode 100644 apps/web/src/routes/_app/index.tsx delete mode 100644 apps/web/src/routes/_app/layout.tsx delete mode 100644 apps/web/src/routes/_app/onboarding/index.tsx delete mode 100644 apps/web/src/routes/_app/onboarding/setup-organization.tsx delete mode 100644 apps/web/src/routes/_app/select-organization/index.tsx delete mode 100644 apps/web/src/routes/_dev/layout.tsx delete mode 100644 apps/web/src/routes/_dev/ui/agent-steps.tsx delete mode 100644 apps/web/src/routes/_dev/ui/layout.tsx delete mode 100644 apps/web/src/routes/dev/embeds/demo.tsx delete mode 100644 apps/web/src/routes/dev/embeds/github.tsx delete mode 100644 apps/web/src/routes/dev/embeds/index.tsx delete mode 100644 apps/web/src/routes/dev/embeds/openstatus.tsx delete mode 100644 apps/web/src/routes/dev/embeds/railway.tsx delete mode 100644 apps/web/src/routes/dev/gallery/$component.tsx delete mode 100644 apps/web/src/routes/join/$slug.tsx delete mode 100644 apps/web/src/routes/sign-in/$.tsx delete mode 100644 apps/web/src/routes/sign-up/$.tsx delete mode 100644 apps/web/src/utils/organization-navigation.ts delete mode 100644 apps/web/src/vite-env.d.ts delete mode 100644 apps/web/tsconfig.json delete mode 100644 apps/web/vite.config.ts delete mode 100644 apps/web/vitest.config.ts delete mode 100644 libs/tanstack-db-atom/README.md delete mode 100644 libs/tanstack-db-atom/package.json delete mode 100644 libs/tanstack-db-atom/src/AtomTanStackDB.result.test.ts delete mode 100644 libs/tanstack-db-atom/src/AtomTanStackDB.subscription.test.ts delete mode 100644 libs/tanstack-db-atom/src/AtomTanStackDB.test.ts delete mode 100644 libs/tanstack-db-atom/src/AtomTanStackDB.timing.test.ts delete mode 100644 libs/tanstack-db-atom/src/AtomTanStackDB.ts delete mode 100644 libs/tanstack-db-atom/src/index.ts delete mode 100644 libs/tanstack-db-atom/src/types.ts delete mode 100644 libs/tanstack-db-atom/tsconfig.json diff --git a/.oxlintrc.json b/.oxlintrc.json index f60fc4842..f30aee6d8 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -18,12 +18,6 @@ ] }, "overrides": [ - { - "files": ["apps/web/src/hooks/use-mount-effect.ts"], - "rules": { - "no-restricted-imports": "off" - } - }, { // Foldkit's own lint rules, scoped to the Foldkit app's source. The plugin is a devDependency // of apps/web-foldkit (isolated linker), so it is loaded by path. @@ -118,6 +112,16 @@ "rules": { "foldkit/no-module-level-mutable-state": "off" } + }, + { + // Framework-free modules carried over from the React app: imperative singletons and + // helpers, not Foldkit programs, so the Foldkit architecture rules do not apply. + "files": ["apps/web-foldkit/src/core/**"], + "rules": { + "foldkit/no-impure-call-at-decision-time": "off", + "foldkit/no-module-level-mutable-state": "off", + "foldkit/no-switch-on-message-tag": "off" + } } ], "ignorePatterns": [ diff --git a/CLAUDE.md b/CLAUDE.md index e6b6a1e01..078ddf40e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is a monorepo with the following structure: -- `apps/web/` - React frontend using Vite, TanStack Router, and TailwindCSS +- `apps/web-foldkit/` - Web frontend built with Foldkit (Effect-based Elm architecture), Vite and TailwindCSS. Also the desktop app's webview - `apps/backendv2/` - Backend API using Bun runtime and Effect-TS - `apps/cluster/` - Effect Cluster service for distributed workflows and background jobs - `packages/db/` - Shared database package: effect-orm table definitions, the `Database` service, and migrations @@ -73,11 +73,12 @@ When working with Effect, Effect Atom, or TanStack DB, refer to these directorie - `bun run test:once` - Run all tests once - `bun run test:coverage` - Run tests with coverage report -### Web App (apps/web) +### Web App (apps/web-foldkit) -- `bun run dev` - Start Vite dev server on port 3000 (DO NOT USE - already running) -- `bun run build` - Build for production and typecheck +- `bun run dev` - Start Vite dev server on port 3100 (DO NOT USE - already running) +- `bun run build` - Build for production - `bun run typecheck` - TypeScript checking without emitting files +- `bun run test` - Run the app's Vitest suite ### Backend (apps/backendv2) @@ -100,15 +101,14 @@ When working with Effect, Effect Atom, or TanStack DB, refer to these directorie ### Frontend (Web App) -- **Framework**: React 19 with TypeScript +- **Framework**: Foldkit (Model / Message / update / view on Effect) with TypeScript - **Build Tool**: Vite -- **Routing**: TanStack Router with file-based routing -- **Styling**: TailwindCSS v4 with Radix UI themes -- **UI Components**: React Aria Components + Ariakit -- **State Management**: TanStack Query + React Form -- **Rich Text**: Plate.js editor with AI features -- **Real-time**: Cloudflare Realtimekit -- **Auth**: Clerk (`@clerk/react`) +- **Routing**: Flat route union in `src/route.ts` +- **Styling**: TailwindCSS v4, `tailwind-variants` styles in `src/core/components/ui/*.styles.ts` +- **UI Components**: `src/ui/` on `@foldkit/ui` +- **Data**: TanStack DB collections synced through Electric (`src/core/db/collections.ts`) +- **Rich Text**: ProseMirror composer (`src/editor/`) +- **Auth**: Hazel auth (`@yielded/auth`) ### Backend @@ -189,11 +189,20 @@ Uses effect-orm (`@maple-dev/effect-orm`, source in `~/Documents/GitHub/effect-c ```typescript import { Database, PG, schema } from "@hazel/db" -const db = yield* Database.Database -const channel = yield* db.first( - PG.from(schema.channelsTable).select().where(($) => [$.id.eq(channelId), $.deletedAt.isNull()]), -) -yield* db.transaction(Effect.gen(function* () { /* every statement in here joins the transaction */ })) +const db = yield * Database.Database +const channel = + yield * + db.first( + PG.from(schema.channelsTable) + .select() + .where(($) => [$.id.eq(channelId), $.deletedAt.isNull()]), + ) +yield * + db.transaction( + Effect.gen(function* () { + /* every statement in here joins the transaction */ + }), + ) ``` - `db.transaction(effect)` pins every statement inside `effect` (repositories included) to one connection through fiber context; there is no transaction handle to pass around. @@ -201,7 +210,6 @@ yield* db.transaction(Effect.gen(function* () { /* every statement in here joins ## Architecture Notes -- Frontend uses file-based routing with TanStack Router - Backend follows Effect-TS patterns for error handling and dependency injection - Real-time features implemented via Cloudflare Realtimekit - Authentication handled by Clerk; backend verifies Clerk-issued JWTs and syncs users/orgs via webhook @@ -214,7 +222,7 @@ yield* db.transaction(Effect.gen(function* () { /* every statement in here joins ### Electric SQL Proxy — Required When Adding New Collections -**IMPORTANT**: When adding a new Electric-synced collection on the frontend (`apps/web/src/db/collections.ts`), you **must also** update the Electric proxy to allow the table: +**IMPORTANT**: When adding a new Electric-synced collection on the frontend (`apps/web-foldkit/src/core/db/collections.ts`), you **must also** update the Electric proxy to allow the table: 1. **Add to `ALLOWED_TABLES`** in `apps/electric-proxy/src/tables/user-tables.ts` 2. **Add a `Match.when` case** in `getWhereClauseForTable` in the same file, using the appropriate WHERE clause builder (e.g., `buildOrgMembershipClause` for org-scoped tables, `buildChannelAccessClause` for channel-scoped tables) @@ -341,7 +349,7 @@ yield * ## Brand Icons -Use Brandfetch CDN for integration brand logos/icons. See `apps/web/src/routes/_app/$orgSlug/settings/integrations/_data.ts` for the helper function. +Use Brandfetch CDN for integration brand logos/icons. See `apps/web-foldkit/src/core/lib/integrations/__data.ts` for the helper function. **URL Pattern**: `https://cdn.brandfetch.io/{domain}/w/{size}/h/{size}/theme/{theme}/{type}` @@ -548,10 +556,10 @@ Hazel Chat serves startup teams, developer teams, and enterprise teams who need **Key design files:** -- Theme tokens: `apps/web/src/styles/theme.css` -- Tailwind config & animations: `apps/web/src/styles/styles.css` -- Theme presets: `apps/web/src/lib/theme/presets.ts` -- Component library: `apps/web/src/components/ui/` +- Theme tokens: `apps/web-foldkit/src/core/styles/theme.css` +- Tailwind config & animations: `apps/web-foldkit/src/core/styles/styles.css` +- Theme presets: `apps/web-foldkit/src/core/lib/theme/presets.ts` +- Component library: `apps/web-foldkit/src/ui/` ### Design Principles diff --git a/alchemy.run.ts b/alchemy.run.ts index e7ce482fa..8437a16aa 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -27,7 +27,7 @@ import BotGateway from "./apps/bot-gateway/alchemy.run.ts" import ElectricProxy from "./apps/electric-proxy/alchemy.run.ts" import Landing from "./apps/landing/alchemy.run.ts" import LinkPreview from "./apps/link-preview-worker/alchemy.run.ts" -import Web from "./apps/web/alchemy.run.ts" +import Web from "./apps/web-foldkit/alchemy.run.ts" // Some secret stores define CLOUDFLARE_DEFAULT_ACCOUNT_ID; alchemy reads CLOUDFLARE_ACCOUNT_ID. if (!process.env.CLOUDFLARE_ACCOUNT_ID && process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID) { diff --git a/apps/web/alchemy.run.ts b/apps/web-foldkit/alchemy.run.ts similarity index 84% rename from apps/web/alchemy.run.ts rename to apps/web-foldkit/alchemy.run.ts index 58abc2637..05c56f495 100644 --- a/apps/web/alchemy.run.ts +++ b/apps/web-foldkit/alchemy.run.ts @@ -1,6 +1,5 @@ /** - * The web SPA, now the Foldkit app (`apps/web-foldkit`): `vite build` output served as Worker - * static assets (no Worker code). + * The web SPA: `vite build` output served as Worker static assets (no Worker code). * `VITE_*` keys in `env` are build inputs: they reach `vite build` via the process env and are * folded into the memo hash, so changing one rebuilds with no source change. */ @@ -13,8 +12,7 @@ const props = Effect.gen(function* () { const { stage, domains, urls } = yield* HazelStack return { name: resolveWorkerName("web", stage), - cwd: new URL("../web-foldkit/", import.meta.url).pathname, - // `bun run build` also runs `tsc`; CI typechecks separately. + cwd: new URL("./", import.meta.url).pathname, command: "bunx vite build", outdir: "dist", dev: { command: "bun run dev" }, @@ -24,13 +22,7 @@ const props = Effect.gen(function* () { }, // Also hash the workspace sources the SPA bundles. memo: { - include: [ - "**/*", - "../web/src/**", - "../web/public/**", - "../../packages/*/src/**", - "../../libs/*/src/**", - ], + include: ["**/*", "../../packages/*/src/**", "../../libs/*/src/**"], lockfile: true, }, compatibility: { date: "2026-10-01" }, diff --git a/apps/web-foldkit/package.json b/apps/web-foldkit/package.json index 6fd85b8ac..b634575e9 100644 --- a/apps/web-foldkit/package.json +++ b/apps/web-foldkit/package.json @@ -15,16 +15,25 @@ "@electric-sql/client": "1.5.28", "@foldkit/ui": "0.167.0", "@fontsource/inter": "^5.2.8", + "@hazel/actors": "workspace:*", "@hazel/auth": "workspace:*", "@hazel/desktop": "workspace:*", "@hazel/domain": "workspace:*", + "@hazel/effect-electric-db-collection": "workspace:*", "@hazel/schema": "workspace:*", "@simplewebauthn/browser": "^14.0.0", "@tanstack/db": "0.11.3", + "@tanstack/hotkeys": "0.4.0", + "@yielded/auth": "0.1.0-beta.38", + "@yielded/auth-simplewebauthn": "0.1.0-beta.38", + "@yielded/crypto": "0.1.0-beta.38", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", "effect": "catalog:effect", "embla-carousel": "8.6.0", "foldkit": "0.167.0", "marked": "^17.0.4", + "prismjs": "^1.30.0", "prosemirror-commands": "^1.7.2", "prosemirror-history": "^1.5.1", "prosemirror-keymap": "^1.2.3", @@ -33,17 +42,18 @@ "prosemirror-transform": "^1.12.2", "prosemirror-view": "^1.42.6", "remend": "^1.2.2", + "rivetkit": "2.1.6", + "slate": "0.123.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", - "tailwind-variants": "^3.2.2", - "@yielded/auth": "0.1.0-beta.38", - "@yielded/auth-simplewebauthn": "0.1.0-beta.38", - "@yielded/crypto": "0.1.0-beta.38" + "tailwind-variants": "^3.2.2" }, "devDependencies": { "@foldkit/devtools": "0.167.0", "@foldkit/oxlint-plugin": "0.15.3", "@foldkit/vite-plugin": "0.27.0", "@tailwindcss/vite": "^4.2.1", + "@types/prismjs": "^1.26.6", "tailwindcss": "^4.2.1", "tailwindcss-react-aria-components": "^2.0.1", "tw-animate-css": "^1.4.0", diff --git a/apps/web/public/apple-touch-icon-180x180.png b/apps/web-foldkit/public/apple-touch-icon-180x180.png similarity index 100% rename from apps/web/public/apple-touch-icon-180x180.png rename to apps/web-foldkit/public/apple-touch-icon-180x180.png diff --git a/apps/web/public/brands/openstatus.svg b/apps/web-foldkit/public/brands/openstatus.svg similarity index 100% rename from apps/web/public/brands/openstatus.svg rename to apps/web-foldkit/public/brands/openstatus.svg diff --git a/apps/web/public/favicon.ico b/apps/web-foldkit/public/favicon.ico similarity index 100% rename from apps/web/public/favicon.ico rename to apps/web-foldkit/public/favicon.ico diff --git a/apps/web/public/icon.svg b/apps/web-foldkit/public/icon.svg similarity index 100% rename from apps/web/public/icon.svg rename to apps/web-foldkit/public/icon.svg diff --git a/apps/web/public/images/image-mask.webp b/apps/web-foldkit/public/images/image-mask.webp similarity index 100% rename from apps/web/public/images/image-mask.webp rename to apps/web-foldkit/public/images/image-mask.webp diff --git a/apps/web/public/images/masks/circle.webp b/apps/web-foldkit/public/images/masks/circle.webp similarity index 100% rename from apps/web/public/images/masks/circle.webp rename to apps/web-foldkit/public/images/masks/circle.webp diff --git a/apps/web/public/images/masks/square.webp b/apps/web-foldkit/public/images/masks/square.webp similarity index 100% rename from apps/web/public/images/masks/square.webp rename to apps/web-foldkit/public/images/masks/square.webp diff --git a/apps/web/public/images/onboarding/autumn-day.webp b/apps/web-foldkit/public/images/onboarding/autumn-day.webp similarity index 100% rename from apps/web/public/images/onboarding/autumn-day.webp rename to apps/web-foldkit/public/images/onboarding/autumn-day.webp diff --git a/apps/web/public/images/onboarding/autumn-night.webp b/apps/web-foldkit/public/images/onboarding/autumn-night.webp similarity index 100% rename from apps/web/public/images/onboarding/autumn-night.webp rename to apps/web-foldkit/public/images/onboarding/autumn-night.webp diff --git a/apps/web/public/images/onboarding/spring-day.webp b/apps/web-foldkit/public/images/onboarding/spring-day.webp similarity index 100% rename from apps/web/public/images/onboarding/spring-day.webp rename to apps/web-foldkit/public/images/onboarding/spring-day.webp diff --git a/apps/web/public/images/onboarding/spring-night.webp b/apps/web-foldkit/public/images/onboarding/spring-night.webp similarity index 100% rename from apps/web/public/images/onboarding/spring-night.webp rename to apps/web-foldkit/public/images/onboarding/spring-night.webp diff --git a/apps/web/public/images/onboarding/summer-day.webp b/apps/web-foldkit/public/images/onboarding/summer-day.webp similarity index 100% rename from apps/web/public/images/onboarding/summer-day.webp rename to apps/web-foldkit/public/images/onboarding/summer-day.webp diff --git a/apps/web/public/images/onboarding/summer-night.webp b/apps/web-foldkit/public/images/onboarding/summer-night.webp similarity index 100% rename from apps/web/public/images/onboarding/summer-night.webp rename to apps/web-foldkit/public/images/onboarding/summer-night.webp diff --git a/apps/web/public/images/onboarding/winter-day.webp b/apps/web-foldkit/public/images/onboarding/winter-day.webp similarity index 100% rename from apps/web/public/images/onboarding/winter-day.webp rename to apps/web-foldkit/public/images/onboarding/winter-day.webp diff --git a/apps/web/public/images/onboarding/winter-night.webp b/apps/web-foldkit/public/images/onboarding/winter-night.webp similarity index 100% rename from apps/web/public/images/onboarding/winter-night.webp rename to apps/web-foldkit/public/images/onboarding/winter-night.webp diff --git a/apps/web/public/images/squirrle_ocean.webp b/apps/web-foldkit/public/images/squirrle_ocean.webp similarity index 100% rename from apps/web/public/images/squirrle_ocean.webp rename to apps/web-foldkit/public/images/squirrle_ocean.webp diff --git a/apps/web/public/images/squirrle_window.webp b/apps/web-foldkit/public/images/squirrle_window.webp similarity index 100% rename from apps/web/public/images/squirrle_window.webp rename to apps/web-foldkit/public/images/squirrle_window.webp diff --git a/apps/web/public/manifest.json b/apps/web-foldkit/public/manifest.json similarity index 100% rename from apps/web/public/manifest.json rename to apps/web-foldkit/public/manifest.json diff --git a/apps/web/public/maskable-icon-512x512.png b/apps/web-foldkit/public/maskable-icon-512x512.png similarity index 100% rename from apps/web/public/maskable-icon-512x512.png rename to apps/web-foldkit/public/maskable-icon-512x512.png diff --git a/apps/web/public/pwa-192x192.png b/apps/web-foldkit/public/pwa-192x192.png similarity index 100% rename from apps/web/public/pwa-192x192.png rename to apps/web-foldkit/public/pwa-192x192.png diff --git a/apps/web/public/pwa-512x512.png b/apps/web-foldkit/public/pwa-512x512.png similarity index 100% rename from apps/web/public/pwa-512x512.png rename to apps/web-foldkit/public/pwa-512x512.png diff --git a/apps/web/public/pwa-64x64.png b/apps/web-foldkit/public/pwa-64x64.png similarity index 100% rename from apps/web/public/pwa-64x64.png rename to apps/web-foldkit/public/pwa-64x64.png diff --git a/apps/web/public/robots.txt b/apps/web-foldkit/public/robots.txt similarity index 100% rename from apps/web/public/robots.txt rename to apps/web-foldkit/public/robots.txt diff --git a/apps/web/public/sounds/README.md b/apps/web-foldkit/public/sounds/README.md similarity index 88% rename from apps/web/public/sounds/README.md rename to apps/web-foldkit/public/sounds/README.md index 2638e17d7..286378bf0 100644 --- a/apps/web/public/sounds/README.md +++ b/apps/web-foldkit/public/sounds/README.md @@ -46,5 +46,5 @@ The notification system currently supports two production sounds: To add new notification sounds to the app: 1. Add the MP3 file to this directory -2. Update the schema in `apps/web/src/hooks/use-notification-sound.tsx` -3. Add the option to the sound list in `apps/web/src/routes/_app/$orgSlug/settings/notifications.tsx` +2. Update the schema in `apps/web-foldkit/src/notification-sound.ts` +3. Add the option to the sound list in `apps/web-foldkit/src/page/my-settings/notifications/view.ts` diff --git a/apps/web/public/sounds/keyboard/.gitkeep b/apps/web-foldkit/public/sounds/keyboard/.gitkeep similarity index 100% rename from apps/web/public/sounds/keyboard/.gitkeep rename to apps/web-foldkit/public/sounds/keyboard/.gitkeep diff --git a/apps/web/public/sounds/keyboard/README.md b/apps/web-foldkit/public/sounds/keyboard/README.md similarity index 100% rename from apps/web/public/sounds/keyboard/README.md rename to apps/web-foldkit/public/sounds/keyboard/README.md diff --git a/apps/web/public/sounds/notification01.mp3 b/apps/web-foldkit/public/sounds/notification01.mp3 similarity index 100% rename from apps/web/public/sounds/notification01.mp3 rename to apps/web-foldkit/public/sounds/notification01.mp3 diff --git a/apps/web/public/sounds/notification03.mp3 b/apps/web-foldkit/public/sounds/notification03.mp3 similarity index 100% rename from apps/web/public/sounds/notification03.mp3 rename to apps/web-foldkit/public/sounds/notification03.mp3 diff --git a/apps/web-foldkit/scripts/generate-appearance-illustrations.ts b/apps/web-foldkit/scripts/generate-appearance-illustrations.ts deleted file mode 100644 index 45ebd5830..000000000 --- a/apps/web-foldkit/scripts/generate-appearance-illustrations.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Generates `src/page/my-settings/appearance/illustrations.ts` from the legacy display-mode - * illustrations (`components/modals/appearances.tsx`), the same way `generate-icons.ts` does: - * bun run scripts/generate-appearance-illustrations.ts - */ -import { writeFileSync } from "node:fs" -import { resolve } from "node:path" -import { htmlChildrenToFoldkit } from "../../../packages/ui-parity/src/codegen.ts" - -const legacyRoot = resolve(import.meta.dir, "../../web") -const { createElement } = await import(Bun.resolveSync("react", legacyRoot)) -const { renderToStaticMarkup } = await import(Bun.resolveSync("react-dom/server", legacyRoot)) -const module = await import(resolve(legacyRoot, "src/components/modals/appearances.tsx")) - -const out: string[] = [ - "// Generated by scripts/generate-appearance-illustrations.ts from apps/web/src/components/modals/appearances.tsx. Do not edit.", - 'import type { Html, HtmlBuilder } from "foldkit/html"', - "", - "const illustration = (h: HtmlBuilder, root: Readonly>, className: string, children: Html[]): Html =>", - "\th.svg(", - "\t\t[...Object.entries(root).map(([key, value]) => h.Attribute(key, value)), h.Class(className)],", - "\t\tchildren,", - "\t)", - "", -] -for (const name of ["System", "Light", "Dark"]) { - const markup = renderToStaticMarkup(createElement(module[name], {})) - const { rootAttributes, children } = htmlChildrenToFoldkit(markup, 2) - const root = Object.fromEntries( - rootAttributes.map((attr) => [attr.name === "viewbox" ? "viewBox" : attr.name, attr.value]), - ) - out.push( - `export const ${name} = (h: HtmlBuilder, className: string): Html =>`, - `\tillustration(h, ${JSON.stringify(root)}, className, [\n${children.join(",\n")},\n\t])`, - "", - ) -} -writeFileSync(resolve(import.meta.dir, "../src/page/my-settings/appearance/illustrations.ts"), out.join("\n")) -console.log("generated 3 illustrations") diff --git a/apps/web-foldkit/scripts/generate-file-icons.ts b/apps/web-foldkit/scripts/generate-file-icons.ts deleted file mode 100644 index f1652c927..000000000 --- a/apps/web-foldkit/scripts/generate-file-icons.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Generates `src/chat/file-icons.ts` from `@untitledui/file-icons`' React `FileIcon`. - * - * Every supported type (plus the "empty" fallback) is rendered to static SVG with - * react-dom/server from apps/web's own dependencies, then stored as a compact element tree: - * bun run scripts/generate-file-icons.ts - */ -import { readFileSync, writeFileSync } from "node:fs" -import { resolve } from "node:path" -import type { DefaultTreeAdapterMap } from "parse5" - -type Node = DefaultTreeAdapterMap["childNode"] -type Element = DefaultTreeAdapterMap["element"] -type Tree = readonly [string, Readonly>, ReadonlyArray] - -const legacyRoot = resolve(import.meta.dir, "../../web") -const { createElement } = await import(Bun.resolveSync("react", legacyRoot)) -const { renderToStaticMarkup } = await import(Bun.resolveSync("react-dom/server", legacyRoot)) -// parse5 is a dependency of the parity package, like the codegen generate-icons.ts uses. -const { parseFragment } = await import( - Bun.resolveSync("parse5", resolve(import.meta.dir, "../../../packages/ui-parity")) -) -const { FileIcon, SUPPORTED_FILE_TYPES } = await import( - Bun.resolveSync("@untitledui/file-icons/dist/FileIcon.mjs", legacyRoot) -) - -// parse5 lowercases SVG attribute and tag names; restore the camelCase ones. -const SVG_CASE: Record = { - viewbox: "viewBox", - clippath: "clipPath", - lineargradient: "linearGradient", - radialgradient: "radialGradient", - gradientunits: "gradientUnits", - gradienttransform: "gradientTransform", -} - -const toTree = (element: Element): Tree => [ - SVG_CASE[element.tagName] ?? element.tagName, - Object.fromEntries(element.attrs.map((attr) => [SVG_CASE[attr.name] ?? attr.name, attr.value])), - (element.childNodes as Node[]).flatMap((child): Array => { - if ("tagName" in child) return [toTree(child as Element)] - if (child.nodeName === "#text") { - const text = (child as DefaultTreeAdapterMap["textNode"]).value - return text.trim() ? [text] : [] - } - return [] - }), -] - -const render = (type: string): Tree => { - const markup = renderToStaticMarkup(createElement(FileIcon, { type })) - const root = parseFragment(markup).childNodes.find((node) => "tagName" in node) - if (!root) throw new Error(`generate-file-icons: no svg for ${type}`) - return toTree(root as Element) -} - -const types: ReadonlyArray = SUPPORTED_FILE_TYPES -const trees = new Map(types.map((type) => [type, render(type)])) -const tags = new Set() -const collectTags = ([tag, , children]: Tree): void => { - tags.add(tag) - for (const child of children) if (typeof child !== "string") collectTags(child) -} -for (const tree of trees.values()) collectTags(tree) - -const out: string[] = [ - "// Generated by scripts/generate-file-icons.ts from @untitledui/file-icons. Do not edit.", - 'import type { Html, HtmlBuilder } from "foldkit/html"', - "", - `type Tag = ${[...tags].map((tag) => JSON.stringify(tag)).join(" | ")}`, - "type Tree = readonly [Tag, Readonly>, ReadonlyArray]", - "", - "export const SUPPORTED_FILE_TYPES: ReadonlyArray = " + JSON.stringify(types), - "", - "/** The FileIcon `default` variant, light theme, keyed by type. */", - "const icons: Readonly> = {", - ...[...trees].map(([type, tree]) => `\t${JSON.stringify(type)}: ${JSON.stringify(tree)},`), - "}", - "", - "// FileIcon resolves a MIME type to its category (`categorizeMimeType`).", - `const codeSubtypes = ${JSON.stringify(mimeList("code"))}`, - `const documentSubtypes = ${JSON.stringify(mimeList("document"))}`, - "const categorize = (mime: string) => {", - '\tconst [kind, subtype] = mime.split("/")', - '\tif (!kind || !subtype) return "unknown"', - '\tif (kind === "image" || kind === "audio" || kind === "video") return kind', - '\tif (kind === "application" || kind === "text") {', - '\t\tif (codeSubtypes.includes(subtype)) return "code"', - '\t\tif (documentSubtypes.includes(subtype)) return "document"', - "\t}", - '\treturn "unknown"', - "}", - "", - "/** FileIcon's type resolution: a known type, a MIME category, else `empty`. */", - "const resolveType = (type: string) => {", - '\tconst isMime = type.includes("/")', - '\tconst subtype = (isMime ? type.split("/")[1] : undefined) ?? type', - "\tif (SUPPORTED_FILE_TYPES.includes(subtype)) return subtype", - "\tconst category = isMime ? categorize(type) : undefined", - '\treturn category !== undefined && SUPPORTED_FILE_TYPES.includes(category) ? category : "empty"', - "}", - "", - "const renderTree = (h: HtmlBuilder, [tag, attributes, children]: Tree, className?: string): Html =>", - "\th[tag](", - "\t\t[", - "\t\t\t...Object.entries(attributes).map(([key, value]) => h.Attribute(key, value)),", - "\t\t\t...(className ? [h.Class(className)] : []),", - "\t\t],", - '\t\tchildren.map((child) => (typeof child === "string" ? child : renderTree(h, child))),', - "\t)", - "", - "/** Port of ``. */", - "export const fileIcon = (", - "\th: HtmlBuilder,", - "\ttype: string,", - "\toptions: { readonly className?: string } = {},", - "): Html => renderTree(h, icons[resolveType(type)] ?? icons.empty!, options.className)", - "", -] - -function mimeList(kind: "code" | "document"): ReadonlyArray { - const text = readFileSync(Bun.resolveSync("@untitledui/file-icons/dist/FileIcon.mjs", legacyRoot), "utf8") - const lists = [...text.matchAll(/=\[("[^\]]*")\]/g)].map( - (match) => JSON.parse(`[${match[1]}]`) as string[], - ) - const found = lists.find((list) => - kind === "code" ? list.includes("typescript") : list.includes("msword"), - ) - if (!found) throw new Error(`generate-file-icons: no ${kind} MIME list`) - return found -} - -const target = resolve(import.meta.dir, "../src/chat/file-icons.ts") -writeFileSync(target, out.join("\n")) -// Format like the rest of the repo so a rerun produces no diff. -Bun.spawnSync(["bunx", "oxfmt", target], { cwd: resolve(import.meta.dir, "../../..") }) -console.log(`generated ${types.length} file icons`) diff --git a/apps/web-foldkit/scripts/generate-icons.ts b/apps/web-foldkit/scripts/generate-icons.ts deleted file mode 100644 index 518d6421f..000000000 --- a/apps/web-foldkit/scripts/generate-icons.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Generates `src/icons/index.ts` from the legacy React icon components. - * - * Each icon is rendered to static SVG with react-dom/server (from apps/web's own - * dependencies), then translated to Foldkit view code. Run after legacy icons change: - * bun run scripts/generate-icons.ts - */ -import { readdirSync, writeFileSync } from "node:fs" -import { resolve } from "node:path" -import { htmlChildrenToFoldkit } from "../../../packages/ui-parity/src/codegen.ts" - -const legacyRoot = resolve(import.meta.dir, "../../web") -const iconsDir = resolve(legacyRoot, "src/components/icons") -const { createElement } = await import(Bun.resolveSync("react", legacyRoot)) -const { renderToStaticMarkup } = await import(Bun.resolveSync("react-dom/server", legacyRoot)) - -const icons = new Map() -for (const file of readdirSync(iconsDir) - .filter((name) => name.endsWith(".tsx")) - .sort()) { - const module = await import(resolve(iconsDir, file)) - for (const [exportName, component] of Object.entries(module)) { - if (exportName === "default" || typeof component !== "function" || !/^Icon/.test(exportName)) continue - icons.set(exportName, renderToStaticMarkup(createElement(component as never, {}))) - } - // Default-only exports: name them after the file. - if ( - module.default && - ![...Object.values(module)].some( - (value, index) => Object.keys(module)[index] !== "default" && value === module.default, - ) - ) { - const name = file - .replace(/\.tsx$/, "") - .split("-") - .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) - .join("") - .replace(/[^A-Za-z0-9]/g, "") - if (!icons.has(name)) icons.set(name, renderToStaticMarkup(createElement(module.default, {}))) - } -} - -// The logo is an icon-shaped component outside the icons directory. -const { Logo } = await import(resolve(legacyRoot, "src/components/logo.tsx")) -icons.set("Logo", renderToStaticMarkup(createElement(Logo, {}))) - -const out: string[] = [ - "// Generated by scripts/generate-icons.ts from apps/web/src/components/icons. Do not edit.", - 'import type { Html, HtmlBuilder } from "foldkit/html"', - "", - "export interface IconOptions {", - "\t/** Extra classes, appended like React's `className` prop. */", - "\treadonly className?: string", - "\t/** Overrides (or with `null` removes) root attributes, like spreading props in the legacy component. */", - "\treadonly attributes?: Readonly>", - "\t/** Replaces the `` text, like the legacy components' `title` prop. */", - "\treadonly title?: string", - "}", - "", - "const icon = <Message>(h: HtmlBuilder<Message>, root: Readonly<Record<string, string>>, options: IconOptions, children: Html[]): Html => {", - "\tconst attributes: Record<string, string | null> = { ...root, ...options.attributes }", - "\tif (options.className) attributes.class = options.className", - "\treturn h.svg(", - "\t\tObject.entries(attributes)", - "\t\t\t.filter((entry): entry is [string, string] => entry[1] !== null)", - '\t\t\t.map(([key, value]) => (key === "class" ? h.Class(value) : h.Attribute(key, value))),', - "\t\tchildren,", - "\t)", - "}", - "", -] -for (const [name, markup] of icons) { - const { rootAttributes, children: rawChildren } = htmlChildrenToFoldkit(markup, 2) - // The legacy icons take a `title` prop for their first <title> child. - const children = rawChildren.map((child, index) => - index === 0 - ? child.replace( - /^(\s*)h\.title\(\s*\[\],\s*\[\s*("(?:[^"\\]|\\.)*"),?\s*\],?\s*\)$/, - "$1h.title([], [options.title ?? $2])", - ) - : child, - ) - const root = Object.fromEntries( - rootAttributes.map((attr) => [attr.name === "viewbox" ? "viewBox" : attr.name, attr.value]), - ) - out.push( - `export const ${name} = <Message>(h: HtmlBuilder<Message>, options: IconOptions = {}): Html =>`, - `\ticon(h, ${JSON.stringify(root)}, options, [\n${children.join(",\n")},\n\t])`, - "", - ) -} -writeFileSync(resolve(import.meta.dir, "../src/icons/index.ts"), out.join("\n")) -console.log(`generated ${icons.size} icons`) diff --git a/apps/web-foldkit/src/chat/file-icons.ts b/apps/web-foldkit/src/chat/file-icons.ts index 62a5ea98b..0a28bc892 100644 --- a/apps/web-foldkit/src/chat/file-icons.ts +++ b/apps/web-foldkit/src/chat/file-icons.ts @@ -1,4 +1,4 @@ -// Generated by scripts/generate-file-icons.ts from @untitledui/file-icons. Do not edit. +// File-type icons, originally generated from @untitledui/file-icons. Edit by hand. import type { Html, HtmlBuilder } from "foldkit/html" type Tag = "svg" | "path" | "rect" | "g" | "defs" | "clipPath" diff --git a/apps/web/src/atoms/emoji-atoms.ts b/apps/web-foldkit/src/core/atoms/emoji-atoms.ts similarity index 100% rename from apps/web/src/atoms/emoji-atoms.ts rename to apps/web-foldkit/src/core/atoms/emoji-atoms.ts diff --git a/apps/web/src/atoms/notification-sound-atoms.test.ts b/apps/web-foldkit/src/core/atoms/notification-sound-atoms.test.ts similarity index 100% rename from apps/web/src/atoms/notification-sound-atoms.test.ts rename to apps/web-foldkit/src/core/atoms/notification-sound-atoms.test.ts diff --git a/apps/web/src/atoms/notification-sound-atoms.ts b/apps/web-foldkit/src/core/atoms/notification-sound-atoms.ts similarity index 100% rename from apps/web/src/atoms/notification-sound-atoms.ts rename to apps/web-foldkit/src/core/atoms/notification-sound-atoms.ts diff --git a/apps/web/src/components/chat/slate-editor/detect-language.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/detect-language.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/detect-language.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/detect-language.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-code-decorator.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-code-decorator.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-code-decorator.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-code-decorator.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-emoji-decorator.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-emoji-decorator.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-emoji-decorator.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-emoji-decorator.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-markdown-decorations.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-decorations.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-markdown-decorations.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-decorations.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-markdown-serializer.test.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-serializer.test.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-markdown-serializer.test.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-serializer.test.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-markdown-serializer.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-serializer.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-markdown-serializer.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-markdown-serializer.ts diff --git a/apps/web/src/components/chat/slate-editor/slate-message-viewer-model.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/slate-message-viewer-model.ts similarity index 100% rename from apps/web/src/components/chat/slate-editor/slate-message-viewer-model.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/slate-message-viewer-model.ts diff --git a/apps/web/src/components/chat/slate-editor/types.ts b/apps/web-foldkit/src/core/components/chat/slate-editor/types.ts similarity index 95% rename from apps/web/src/components/chat/slate-editor/types.ts rename to apps/web-foldkit/src/core/components/chat/slate-editor/types.ts index be19c9e57..1f4b7c13d 100644 --- a/apps/web/src/components/chat/slate-editor/types.ts +++ b/apps/web-foldkit/src/core/components/chat/slate-editor/types.ts @@ -1,6 +1,4 @@ import type { BaseEditor, BaseElement, BaseText, Descendant } from "slate" -import type { HistoryEditor } from "slate-history" -import type { ReactEditor } from "slate-react" // Custom Element Types export interface CodeBlockElement extends BaseElement { @@ -102,7 +100,7 @@ export type MarkdownLeaf = BaseText & { // Extend Slate's types declare module "slate" { interface CustomTypes { - Editor: BaseEditor & ReactEditor & HistoryEditor + Editor: BaseEditor Element: CustomElement Text: MarkdownLeaf } diff --git a/apps/web/src/components/embeds/embed-markdown.parser.ts b/apps/web-foldkit/src/core/components/embeds/embed-markdown.parser.ts similarity index 100% rename from apps/web/src/components/embeds/embed-markdown.parser.ts rename to apps/web-foldkit/src/core/components/embeds/embed-markdown.parser.ts diff --git a/apps/web/src/components/embeds/embed-markdown.styles.ts b/apps/web-foldkit/src/core/components/embeds/embed-markdown.styles.ts similarity index 100% rename from apps/web/src/components/embeds/embed-markdown.styles.ts rename to apps/web-foldkit/src/core/components/embeds/embed-markdown.styles.ts diff --git a/apps/web/src/components/embeds/embed.styles.ts b/apps/web-foldkit/src/core/components/embeds/embed.styles.ts similarity index 100% rename from apps/web/src/components/embeds/embed.styles.ts rename to apps/web-foldkit/src/core/components/embeds/embed.styles.ts diff --git a/apps/web/src/components/link-preview.utils.ts b/apps/web-foldkit/src/core/components/link-preview.utils.ts similarity index 100% rename from apps/web/src/components/link-preview.utils.ts rename to apps/web-foldkit/src/core/components/link-preview.utils.ts diff --git a/apps/web/src/components/ui/avatar/styles.ts b/apps/web-foldkit/src/core/components/ui/avatar/styles.ts similarity index 92% rename from apps/web/src/components/ui/avatar/styles.ts rename to apps/web-foldkit/src/core/components/ui/avatar/styles.ts index 688d77f41..b0f5f4ea0 100644 --- a/apps/web/src/components/ui/avatar/styles.ts +++ b/apps/web-foldkit/src/core/components/ui/avatar/styles.ts @@ -1,4 +1,18 @@ -import type { AvatarSize } from "./avatar-context" +export type AvatarSize = + | "xxs" + | "xs" + | "sm" + | "md" + | "lg" + | "xl" + | "2xl" + | "3xl" + | "4xl" + | "5xl" + | "6xl" + | "7xl" + | "8xl" + | "9xl" export const styles: Record<AvatarSize, { root: string; initials: string; icon: string }> = { xxs: { diff --git a/apps/web/src/components/ui/badge.styles.ts b/apps/web-foldkit/src/core/components/ui/badge.styles.ts similarity index 100% rename from apps/web/src/components/ui/badge.styles.ts rename to apps/web-foldkit/src/core/components/ui/badge.styles.ts diff --git a/apps/web/src/components/ui/button.styles.ts b/apps/web-foldkit/src/core/components/ui/button.styles.ts similarity index 100% rename from apps/web/src/components/ui/button.styles.ts rename to apps/web-foldkit/src/core/components/ui/button.styles.ts diff --git a/apps/web/src/components/ui/calendar.styles.ts b/apps/web-foldkit/src/core/components/ui/calendar.styles.ts similarity index 100% rename from apps/web/src/components/ui/calendar.styles.ts rename to apps/web-foldkit/src/core/components/ui/calendar.styles.ts diff --git a/apps/web/src/components/ui/card.styles.ts b/apps/web-foldkit/src/core/components/ui/card.styles.ts similarity index 100% rename from apps/web/src/components/ui/card.styles.ts rename to apps/web-foldkit/src/core/components/ui/card.styles.ts diff --git a/apps/web/src/components/ui/checkbox.styles.ts b/apps/web-foldkit/src/core/components/ui/checkbox.styles.ts similarity index 100% rename from apps/web/src/components/ui/checkbox.styles.ts rename to apps/web-foldkit/src/core/components/ui/checkbox.styles.ts diff --git a/apps/web/src/components/ui/choice-box.styles.ts b/apps/web-foldkit/src/core/components/ui/choice-box.styles.ts similarity index 100% rename from apps/web/src/components/ui/choice-box.styles.ts rename to apps/web-foldkit/src/core/components/ui/choice-box.styles.ts diff --git a/apps/web/src/components/ui/combo-box.styles.ts b/apps/web-foldkit/src/core/components/ui/combo-box.styles.ts similarity index 100% rename from apps/web/src/components/ui/combo-box.styles.ts rename to apps/web-foldkit/src/core/components/ui/combo-box.styles.ts diff --git a/apps/web/src/components/ui/command-menu-form.styles.ts b/apps/web-foldkit/src/core/components/ui/command-menu-form.styles.ts similarity index 100% rename from apps/web/src/components/ui/command-menu-form.styles.ts rename to apps/web-foldkit/src/core/components/ui/command-menu-form.styles.ts diff --git a/apps/web/src/components/ui/command-menu.styles.ts b/apps/web-foldkit/src/core/components/ui/command-menu.styles.ts similarity index 100% rename from apps/web/src/components/ui/command-menu.styles.ts rename to apps/web-foldkit/src/core/components/ui/command-menu.styles.ts diff --git a/apps/web/src/components/ui/context-menu.styles.ts b/apps/web-foldkit/src/core/components/ui/context-menu.styles.ts similarity index 100% rename from apps/web/src/components/ui/context-menu.styles.ts rename to apps/web-foldkit/src/core/components/ui/context-menu.styles.ts diff --git a/apps/web/src/components/ui/date-field.styles.ts b/apps/web-foldkit/src/core/components/ui/date-field.styles.ts similarity index 100% rename from apps/web/src/components/ui/date-field.styles.ts rename to apps/web-foldkit/src/core/components/ui/date-field.styles.ts diff --git a/apps/web/src/components/ui/date-picker.styles.ts b/apps/web-foldkit/src/core/components/ui/date-picker.styles.ts similarity index 100% rename from apps/web/src/components/ui/date-picker.styles.ts rename to apps/web-foldkit/src/core/components/ui/date-picker.styles.ts diff --git a/apps/web/src/components/ui/dialog.styles.ts b/apps/web-foldkit/src/core/components/ui/dialog.styles.ts similarity index 100% rename from apps/web/src/components/ui/dialog.styles.ts rename to apps/web-foldkit/src/core/components/ui/dialog.styles.ts diff --git a/apps/web/src/components/ui/dropdown.styles.ts b/apps/web-foldkit/src/core/components/ui/dropdown.styles.ts similarity index 100% rename from apps/web/src/components/ui/dropdown.styles.ts rename to apps/web-foldkit/src/core/components/ui/dropdown.styles.ts diff --git a/apps/web/src/components/ui/empty-state.styles.ts b/apps/web-foldkit/src/core/components/ui/empty-state.styles.ts similarity index 100% rename from apps/web/src/components/ui/empty-state.styles.ts rename to apps/web-foldkit/src/core/components/ui/empty-state.styles.ts diff --git a/apps/web/src/components/ui/field-validation-state.styles.ts b/apps/web-foldkit/src/core/components/ui/field-validation-state.styles.ts similarity index 100% rename from apps/web/src/components/ui/field-validation-state.styles.ts rename to apps/web-foldkit/src/core/components/ui/field-validation-state.styles.ts diff --git a/apps/web/src/components/ui/field.styles.ts b/apps/web-foldkit/src/core/components/ui/field.styles.ts similarity index 100% rename from apps/web/src/components/ui/field.styles.ts rename to apps/web-foldkit/src/core/components/ui/field.styles.ts diff --git a/apps/web/src/components/ui/form-error-summary.styles.ts b/apps/web-foldkit/src/core/components/ui/form-error-summary.styles.ts similarity index 100% rename from apps/web/src/components/ui/form-error-summary.styles.ts rename to apps/web-foldkit/src/core/components/ui/form-error-summary.styles.ts diff --git a/apps/web/src/components/ui/input-otp.styles.ts b/apps/web-foldkit/src/core/components/ui/input-otp.styles.ts similarity index 100% rename from apps/web/src/components/ui/input-otp.styles.ts rename to apps/web-foldkit/src/core/components/ui/input-otp.styles.ts diff --git a/apps/web/src/components/ui/input.styles.ts b/apps/web-foldkit/src/core/components/ui/input.styles.ts similarity index 100% rename from apps/web/src/components/ui/input.styles.ts rename to apps/web-foldkit/src/core/components/ui/input.styles.ts diff --git a/apps/web/src/components/ui/keyboard.styles.ts b/apps/web-foldkit/src/core/components/ui/keyboard.styles.ts similarity index 100% rename from apps/web/src/components/ui/keyboard.styles.ts rename to apps/web-foldkit/src/core/components/ui/keyboard.styles.ts diff --git a/apps/web/src/components/ui/link.styles.ts b/apps/web-foldkit/src/core/components/ui/link.styles.ts similarity index 100% rename from apps/web/src/components/ui/link.styles.ts rename to apps/web-foldkit/src/core/components/ui/link.styles.ts diff --git a/apps/web/src/components/ui/list-box.styles.ts b/apps/web-foldkit/src/core/components/ui/list-box.styles.ts similarity index 100% rename from apps/web/src/components/ui/list-box.styles.ts rename to apps/web-foldkit/src/core/components/ui/list-box.styles.ts diff --git a/apps/web/src/components/ui/loader.styles.ts b/apps/web-foldkit/src/core/components/ui/loader.styles.ts similarity index 100% rename from apps/web/src/components/ui/loader.styles.ts rename to apps/web-foldkit/src/core/components/ui/loader.styles.ts diff --git a/apps/web/src/components/ui/menu.styles.ts b/apps/web-foldkit/src/core/components/ui/menu.styles.ts similarity index 100% rename from apps/web/src/components/ui/menu.styles.ts rename to apps/web-foldkit/src/core/components/ui/menu.styles.ts diff --git a/apps/web/src/components/ui/modal.styles.ts b/apps/web-foldkit/src/core/components/ui/modal.styles.ts similarity index 100% rename from apps/web/src/components/ui/modal.styles.ts rename to apps/web-foldkit/src/core/components/ui/modal.styles.ts diff --git a/apps/web/src/components/ui/popover.styles.ts b/apps/web-foldkit/src/core/components/ui/popover.styles.ts similarity index 100% rename from apps/web/src/components/ui/popover.styles.ts rename to apps/web-foldkit/src/core/components/ui/popover.styles.ts diff --git a/apps/web/src/components/ui/progress-bar.styles.ts b/apps/web-foldkit/src/core/components/ui/progress-bar.styles.ts similarity index 100% rename from apps/web/src/components/ui/progress-bar.styles.ts rename to apps/web-foldkit/src/core/components/ui/progress-bar.styles.ts diff --git a/apps/web/src/components/ui/radio.styles.ts b/apps/web-foldkit/src/core/components/ui/radio.styles.ts similarity index 100% rename from apps/web/src/components/ui/radio.styles.ts rename to apps/web-foldkit/src/core/components/ui/radio.styles.ts diff --git a/apps/web/src/components/ui/search-field.styles.ts b/apps/web-foldkit/src/core/components/ui/search-field.styles.ts similarity index 100% rename from apps/web/src/components/ui/search-field.styles.ts rename to apps/web-foldkit/src/core/components/ui/search-field.styles.ts diff --git a/apps/web/src/components/ui/section-footer.styles.ts b/apps/web-foldkit/src/core/components/ui/section-footer.styles.ts similarity index 100% rename from apps/web/src/components/ui/section-footer.styles.ts rename to apps/web-foldkit/src/core/components/ui/section-footer.styles.ts diff --git a/apps/web/src/components/ui/section-header.styles.ts b/apps/web-foldkit/src/core/components/ui/section-header.styles.ts similarity index 100% rename from apps/web/src/components/ui/section-header.styles.ts rename to apps/web-foldkit/src/core/components/ui/section-header.styles.ts diff --git a/apps/web/src/components/ui/section-label.styles.ts b/apps/web-foldkit/src/core/components/ui/section-label.styles.ts similarity index 100% rename from apps/web/src/components/ui/section-label.styles.ts rename to apps/web-foldkit/src/core/components/ui/section-label.styles.ts diff --git a/apps/web/src/components/ui/select.styles.ts b/apps/web-foldkit/src/core/components/ui/select.styles.ts similarity index 100% rename from apps/web/src/components/ui/select.styles.ts rename to apps/web-foldkit/src/core/components/ui/select.styles.ts diff --git a/apps/web/src/components/ui/separator.styles.ts b/apps/web-foldkit/src/core/components/ui/separator.styles.ts similarity index 100% rename from apps/web/src/components/ui/separator.styles.ts rename to apps/web-foldkit/src/core/components/ui/separator.styles.ts diff --git a/apps/web/src/components/ui/sheet.styles.ts b/apps/web-foldkit/src/core/components/ui/sheet.styles.ts similarity index 100% rename from apps/web/src/components/ui/sheet.styles.ts rename to apps/web-foldkit/src/core/components/ui/sheet.styles.ts diff --git a/apps/web/src/components/ui/slider.styles.ts b/apps/web-foldkit/src/core/components/ui/slider.styles.ts similarity index 100% rename from apps/web/src/components/ui/slider.styles.ts rename to apps/web-foldkit/src/core/components/ui/slider.styles.ts diff --git a/apps/web/src/components/ui/sonner.styles.ts b/apps/web-foldkit/src/core/components/ui/sonner.styles.ts similarity index 100% rename from apps/web/src/components/ui/sonner.styles.ts rename to apps/web-foldkit/src/core/components/ui/sonner.styles.ts diff --git a/apps/web/src/components/ui/split-panel/split-panel.styles.ts b/apps/web-foldkit/src/core/components/ui/split-panel/split-panel.styles.ts similarity index 100% rename from apps/web/src/components/ui/split-panel/split-panel.styles.ts rename to apps/web-foldkit/src/core/components/ui/split-panel/split-panel.styles.ts diff --git a/apps/web/src/components/ui/switch.styles.ts b/apps/web-foldkit/src/core/components/ui/switch.styles.ts similarity index 100% rename from apps/web/src/components/ui/switch.styles.ts rename to apps/web-foldkit/src/core/components/ui/switch.styles.ts diff --git a/apps/web/src/components/ui/table.styles.ts b/apps/web-foldkit/src/core/components/ui/table.styles.ts similarity index 100% rename from apps/web/src/components/ui/table.styles.ts rename to apps/web-foldkit/src/core/components/ui/table.styles.ts diff --git a/apps/web/src/components/ui/tabs.styles.ts b/apps/web-foldkit/src/core/components/ui/tabs.styles.ts similarity index 100% rename from apps/web/src/components/ui/tabs.styles.ts rename to apps/web-foldkit/src/core/components/ui/tabs.styles.ts diff --git a/apps/web/src/components/ui/text.styles.ts b/apps/web-foldkit/src/core/components/ui/text.styles.ts similarity index 100% rename from apps/web/src/components/ui/text.styles.ts rename to apps/web-foldkit/src/core/components/ui/text.styles.ts diff --git a/apps/web/src/components/ui/textarea.styles.ts b/apps/web-foldkit/src/core/components/ui/textarea.styles.ts similarity index 100% rename from apps/web/src/components/ui/textarea.styles.ts rename to apps/web-foldkit/src/core/components/ui/textarea.styles.ts diff --git a/apps/web/src/components/ui/toast.styles.ts b/apps/web-foldkit/src/core/components/ui/toast.styles.ts similarity index 100% rename from apps/web/src/components/ui/toast.styles.ts rename to apps/web-foldkit/src/core/components/ui/toast.styles.ts diff --git a/apps/web/src/components/ui/toggle-group.styles.ts b/apps/web-foldkit/src/core/components/ui/toggle-group.styles.ts similarity index 100% rename from apps/web/src/components/ui/toggle-group.styles.ts rename to apps/web-foldkit/src/core/components/ui/toggle-group.styles.ts diff --git a/apps/web/src/components/ui/toggle.styles.ts b/apps/web-foldkit/src/core/components/ui/toggle.styles.ts similarity index 100% rename from apps/web/src/components/ui/toggle.styles.ts rename to apps/web-foldkit/src/core/components/ui/toggle.styles.ts diff --git a/apps/web/src/components/ui/toolbar.styles.ts b/apps/web-foldkit/src/core/components/ui/toolbar.styles.ts similarity index 100% rename from apps/web/src/components/ui/toolbar.styles.ts rename to apps/web-foldkit/src/core/components/ui/toolbar.styles.ts diff --git a/apps/web/src/components/ui/tooltip.styles.ts b/apps/web-foldkit/src/core/components/ui/tooltip.styles.ts similarity index 100% rename from apps/web/src/components/ui/tooltip.styles.ts rename to apps/web-foldkit/src/core/components/ui/tooltip.styles.ts diff --git a/apps/web/src/components/ui/tree.styles.ts b/apps/web-foldkit/src/core/components/ui/tree.styles.ts similarity index 100% rename from apps/web/src/components/ui/tree.styles.ts rename to apps/web-foldkit/src/core/components/ui/tree.styles.ts diff --git a/apps/web/src/db/actions.ts b/apps/web-foldkit/src/core/db/actions.ts similarity index 99% rename from apps/web/src/db/actions.ts rename to apps/web-foldkit/src/core/db/actions.ts index b6d42c3e7..43c2c00fc 100644 --- a/apps/web/src/db/actions.ts +++ b/apps/web-foldkit/src/core/db/actions.ts @@ -26,7 +26,7 @@ const isErrorRetryable = (error: unknown): boolean => { import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" import { runtime } from "~/lib/services/common/runtime" import { getSharedConversationIdForChannel } from "~/lib/connect-shared-channels" -import { optimisticAction } from "../../../../libs/effect-electric-db-collection/src" +import { optimisticAction } from "@hazel/effect-electric-db-collection" import { attachmentCollection, channelCollection, diff --git a/apps/web/src/db/collections.ts b/apps/web-foldkit/src/core/db/collections.ts similarity index 99% rename from apps/web/src/db/collections.ts rename to apps/web-foldkit/src/core/db/collections.ts index a590b8e7c..ded735c6a 100644 --- a/apps/web/src/db/collections.ts +++ b/apps/web-foldkit/src/core/db/collections.ts @@ -26,7 +26,7 @@ import { } from "@hazel/domain/models" import { electricFetchClient } from "~/lib/electric-fetch" import { runtime } from "~/lib/services/common/runtime" -import { createEffectCollection } from "../../../../libs/effect-electric-db-collection/src" +import { createEffectCollection } from "@hazel/effect-electric-db-collection" const electricUrl = import.meta.env.VITE_ELECTRIC_URL diff --git a/apps/web/src/dev-gallery/gallery.styles.ts b/apps/web-foldkit/src/core/dev-gallery/gallery.styles.ts similarity index 100% rename from apps/web/src/dev-gallery/gallery.styles.ts rename to apps/web-foldkit/src/core/dev-gallery/gallery.styles.ts diff --git a/apps/web/src/lib/access-token.test.ts b/apps/web-foldkit/src/core/lib/access-token.test.ts similarity index 97% rename from apps/web/src/lib/access-token.test.ts rename to apps/web-foldkit/src/core/lib/access-token.test.ts index b1fcb01cd..bb7ae94ce 100644 --- a/apps/web/src/lib/access-token.test.ts +++ b/apps/web-foldkit/src/core/lib/access-token.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from "vitest" import { clearAccessToken, getAccessToken } from "./access-token" diff --git a/apps/web/src/lib/access-token.ts b/apps/web-foldkit/src/core/lib/access-token.ts similarity index 100% rename from apps/web/src/lib/access-token.ts rename to apps/web-foldkit/src/core/lib/access-token.ts diff --git a/apps/web/src/lib/auth-fetch.ts b/apps/web-foldkit/src/core/lib/auth-fetch.ts similarity index 100% rename from apps/web/src/lib/auth-fetch.ts rename to apps/web-foldkit/src/core/lib/auth-fetch.ts diff --git a/apps/web/src/lib/bot-avatar.ts b/apps/web-foldkit/src/core/lib/bot-avatar.ts similarity index 100% rename from apps/web/src/lib/bot-avatar.ts rename to apps/web-foldkit/src/core/lib/bot-avatar.ts diff --git a/apps/web/src/lib/bot-scopes.ts b/apps/web-foldkit/src/core/lib/bot-scopes.ts similarity index 100% rename from apps/web/src/lib/bot-scopes.ts rename to apps/web-foldkit/src/core/lib/bot-scopes.ts diff --git a/apps/web/src/lib/connect-shared-channels.test.ts b/apps/web-foldkit/src/core/lib/connect-shared-channels.test.ts similarity index 100% rename from apps/web/src/lib/connect-shared-channels.test.ts rename to apps/web-foldkit/src/core/lib/connect-shared-channels.test.ts diff --git a/apps/web/src/lib/connect-shared-channels.ts b/apps/web-foldkit/src/core/lib/connect-shared-channels.ts similarity index 100% rename from apps/web/src/lib/connect-shared-channels.ts rename to apps/web-foldkit/src/core/lib/connect-shared-channels.ts diff --git a/apps/web/src/lib/custom-emoji-utils.ts b/apps/web-foldkit/src/core/lib/custom-emoji-utils.ts similarity index 100% rename from apps/web/src/lib/custom-emoji-utils.ts rename to apps/web-foldkit/src/core/lib/custom-emoji-utils.ts diff --git a/apps/web/src/lib/date-fns-shared.ts b/apps/web-foldkit/src/core/lib/date-fns-shared.ts similarity index 100% rename from apps/web/src/lib/date-fns-shared.ts rename to apps/web-foldkit/src/core/lib/date-fns-shared.ts diff --git a/apps/web/src/lib/electric-fetch.ts b/apps/web-foldkit/src/core/lib/electric-fetch.ts similarity index 100% rename from apps/web/src/lib/electric-fetch.ts rename to apps/web-foldkit/src/core/lib/electric-fetch.ts diff --git a/apps/web/src/lib/emoji-shortcode-map.ts b/apps/web-foldkit/src/core/lib/emoji-shortcode-map.ts similarity index 100% rename from apps/web/src/lib/emoji-shortcode-map.ts rename to apps/web-foldkit/src/core/lib/emoji-shortcode-map.ts diff --git a/apps/web/src/lib/emoji-utils.ts b/apps/web-foldkit/src/core/lib/emoji-utils.ts similarity index 100% rename from apps/web/src/lib/emoji-utils.ts rename to apps/web-foldkit/src/core/lib/emoji-utils.ts diff --git a/apps/web/src/lib/error-messages.ts b/apps/web-foldkit/src/core/lib/error-messages.ts similarity index 99% rename from apps/web/src/lib/error-messages.ts rename to apps/web-foldkit/src/core/lib/error-messages.ts index 6e200c9ed..0d3efe9ee 100644 --- a/apps/web/src/lib/error-messages.ts +++ b/apps/web-foldkit/src/core/lib/error-messages.ts @@ -43,7 +43,7 @@ import { SyncError, TransactionStateEffectError, UndefinedKeyEffectError, -} from "../../../../libs/effect-electric-db-collection/src" +} from "@hazel/effect-electric-db-collection" /** * User-friendly error message configuration diff --git a/apps/web/src/lib/helper/generate-shades.ts b/apps/web-foldkit/src/core/lib/helper/generate-shades.ts similarity index 100% rename from apps/web/src/lib/helper/generate-shades.ts rename to apps/web-foldkit/src/core/lib/helper/generate-shades.ts diff --git a/apps/web/src/lib/hotkeys/hotkey-registry.test.ts b/apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.test.ts similarity index 94% rename from apps/web/src/lib/hotkeys/hotkey-registry.test.ts rename to apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.test.ts index 509f11aaa..79260a5e9 100644 --- a/apps/web/src/lib/hotkeys/hotkey-registry.test.ts +++ b/apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.test.ts @@ -1,4 +1,4 @@ -import { validateHotkey } from "@tanstack/react-hotkeys" +import { validateHotkey } from "@tanstack/hotkeys" import { describe, expect, it } from "vitest" import { HOTKEY_DEFINITIONS, HOTKEY_DEFINITIONS_BY_ID, isAppHotkeyActionId } from "./hotkey-registry" diff --git a/apps/web/src/lib/hotkeys/hotkey-registry.ts b/apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.ts similarity index 97% rename from apps/web/src/lib/hotkeys/hotkey-registry.ts rename to apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.ts index 477ba7a54..a0efd125b 100644 --- a/apps/web/src/lib/hotkeys/hotkey-registry.ts +++ b/apps/web-foldkit/src/core/lib/hotkeys/hotkey-registry.ts @@ -1,4 +1,4 @@ -import type { Hotkey } from "@tanstack/react-hotkeys" +import type { Hotkey } from "@tanstack/hotkeys" export type AppHotkeyCategory = "global" | "navigation" | "modal" diff --git a/apps/web/src/lib/integrations/__data.ts b/apps/web-foldkit/src/core/lib/integrations/__data.ts similarity index 100% rename from apps/web/src/lib/integrations/__data.ts rename to apps/web-foldkit/src/core/lib/integrations/__data.ts diff --git a/apps/web/src/lib/native-notifications.test.ts b/apps/web-foldkit/src/core/lib/native-notifications.test.ts similarity index 98% rename from apps/web/src/lib/native-notifications.test.ts rename to apps/web-foldkit/src/core/lib/native-notifications.test.ts index 9baf4d222..e9b5eaa15 100644 --- a/apps/web/src/lib/native-notifications.test.ts +++ b/apps/web-foldkit/src/core/lib/native-notifications.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment jsdom import { describe, expect, it, vi } from "vitest" import { buildNotificationContent, diff --git a/apps/web/src/lib/native-notifications.ts b/apps/web-foldkit/src/core/lib/native-notifications.ts similarity index 100% rename from apps/web/src/lib/native-notifications.ts rename to apps/web-foldkit/src/core/lib/native-notifications.ts diff --git a/apps/web/src/lib/notification-sound-manager.test.ts b/apps/web-foldkit/src/core/lib/notification-sound-manager.test.ts similarity index 100% rename from apps/web/src/lib/notification-sound-manager.test.ts rename to apps/web-foldkit/src/core/lib/notification-sound-manager.test.ts diff --git a/apps/web/src/lib/notification-sound-manager.ts b/apps/web-foldkit/src/core/lib/notification-sound-manager.ts similarity index 100% rename from apps/web/src/lib/notification-sound-manager.ts rename to apps/web-foldkit/src/core/lib/notification-sound-manager.ts diff --git a/apps/web/src/lib/notifications/README.md b/apps/web-foldkit/src/core/lib/notifications/README.md similarity index 91% rename from apps/web/src/lib/notifications/README.md rename to apps/web-foldkit/src/core/lib/notifications/README.md index 217e73bac..33dbdafa0 100644 --- a/apps/web/src/lib/notifications/README.md +++ b/apps/web-foldkit/src/core/lib/notifications/README.md @@ -14,5 +14,4 @@ This module centralizes desktop notification behavior for Hazel. - `types.ts`: shared contracts for decisions, events, sink outcomes. - `orchestrator.ts`: queueing, dedupe, decisioning, sink fan-out. - `sinks/`: concrete sound/native/in-app handlers. -- `selectors.ts`: shared unread-count selectors used by hooks and UI. - `diagnostics-store.ts`: in-memory diagnostics timeline for debug UI. diff --git a/apps/web/src/lib/notifications/diagnostics-store.ts b/apps/web-foldkit/src/core/lib/notifications/diagnostics-store.ts similarity index 100% rename from apps/web/src/lib/notifications/diagnostics-store.ts rename to apps/web-foldkit/src/core/lib/notifications/diagnostics-store.ts diff --git a/apps/web/src/lib/notifications/index.ts b/apps/web-foldkit/src/core/lib/notifications/index.ts similarity index 82% rename from apps/web/src/lib/notifications/index.ts rename to apps/web-foldkit/src/core/lib/notifications/index.ts index 51c10e1af..2ddc8050f 100644 --- a/apps/web/src/lib/notifications/index.ts +++ b/apps/web-foldkit/src/core/lib/notifications/index.ts @@ -4,12 +4,6 @@ export { subscribeNotificationDiagnostics, } from "./diagnostics-store" export { notificationOrchestrator, NotificationOrchestrator } from "./orchestrator" -export { - isUnreadNotification, - selectUnreadCount, - selectUnreadCountsByChannel, - useNotificationUnreadCountsByChannel, -} from "./selectors" export { inAppNotificationSink, InAppNotificationSink } from "./sinks/in-app" export { NativeNotificationSink } from "./sinks/native" export { SoundNotificationSink } from "./sinks/sound" diff --git a/apps/web/src/lib/notifications/orchestrator.test.ts b/apps/web-foldkit/src/core/lib/notifications/orchestrator.test.ts similarity index 100% rename from apps/web/src/lib/notifications/orchestrator.test.ts rename to apps/web-foldkit/src/core/lib/notifications/orchestrator.test.ts diff --git a/apps/web/src/lib/notifications/orchestrator.ts b/apps/web-foldkit/src/core/lib/notifications/orchestrator.ts similarity index 100% rename from apps/web/src/lib/notifications/orchestrator.ts rename to apps/web-foldkit/src/core/lib/notifications/orchestrator.ts diff --git a/apps/web/src/lib/notifications/sinks/in-app.ts b/apps/web-foldkit/src/core/lib/notifications/sinks/in-app.ts similarity index 100% rename from apps/web/src/lib/notifications/sinks/in-app.ts rename to apps/web-foldkit/src/core/lib/notifications/sinks/in-app.ts diff --git a/apps/web/src/lib/notifications/sinks/native.ts b/apps/web-foldkit/src/core/lib/notifications/sinks/native.ts similarity index 100% rename from apps/web/src/lib/notifications/sinks/native.ts rename to apps/web-foldkit/src/core/lib/notifications/sinks/native.ts diff --git a/apps/web/src/lib/notifications/sinks/sound.ts b/apps/web-foldkit/src/core/lib/notifications/sinks/sound.ts similarity index 100% rename from apps/web/src/lib/notifications/sinks/sound.ts rename to apps/web-foldkit/src/core/lib/notifications/sinks/sound.ts diff --git a/apps/web/src/lib/notifications/types.ts b/apps/web-foldkit/src/core/lib/notifications/types.ts similarity index 100% rename from apps/web/src/lib/notifications/types.ts rename to apps/web-foldkit/src/core/lib/notifications/types.ts diff --git a/apps/web/src/lib/platform-storage/index.ts b/apps/web-foldkit/src/core/lib/platform-storage/index.ts similarity index 100% rename from apps/web/src/lib/platform-storage/index.ts rename to apps/web-foldkit/src/core/lib/platform-storage/index.ts diff --git a/apps/web/src/lib/platform-storage/platform-key-value-store.ts b/apps/web-foldkit/src/core/lib/platform-storage/platform-key-value-store.ts similarity index 100% rename from apps/web/src/lib/platform-storage/platform-key-value-store.ts rename to apps/web-foldkit/src/core/lib/platform-storage/platform-key-value-store.ts diff --git a/apps/web/src/lib/platform-storage/platform-runtime.ts b/apps/web-foldkit/src/core/lib/platform-storage/platform-runtime.ts similarity index 100% rename from apps/web/src/lib/platform-storage/platform-runtime.ts rename to apps/web-foldkit/src/core/lib/platform-storage/platform-runtime.ts diff --git a/apps/web/src/lib/platform-storage/tauri-key-value-store.ts b/apps/web-foldkit/src/core/lib/platform-storage/tauri-key-value-store.ts similarity index 100% rename from apps/web/src/lib/platform-storage/tauri-key-value-store.ts rename to apps/web-foldkit/src/core/lib/platform-storage/tauri-key-value-store.ts diff --git a/apps/web/src/lib/rivet-client.ts b/apps/web-foldkit/src/core/lib/rivet-client.ts similarity index 100% rename from apps/web/src/lib/rivet-client.ts rename to apps/web-foldkit/src/core/lib/rivet-client.ts diff --git a/apps/web/src/lib/rpc-auth-middleware.ts b/apps/web-foldkit/src/core/lib/rpc-auth-middleware.ts similarity index 100% rename from apps/web/src/lib/rpc-auth-middleware.ts rename to apps/web-foldkit/src/core/lib/rpc-auth-middleware.ts diff --git a/apps/web/src/lib/search-filter-parser.ts b/apps/web-foldkit/src/core/lib/search-filter-parser.ts similarity index 100% rename from apps/web/src/lib/search-filter-parser.ts rename to apps/web-foldkit/src/core/lib/search-filter-parser.ts diff --git a/apps/web/src/lib/services/common/api-client.ts b/apps/web-foldkit/src/core/lib/services/common/api-client.ts similarity index 100% rename from apps/web/src/lib/services/common/api-client.ts rename to apps/web-foldkit/src/core/lib/services/common/api-client.ts diff --git a/apps/web/src/lib/services/common/atom-client.ts b/apps/web-foldkit/src/core/lib/services/common/atom-client.ts similarity index 100% rename from apps/web/src/lib/services/common/atom-client.ts rename to apps/web-foldkit/src/core/lib/services/common/atom-client.ts diff --git a/apps/web/src/lib/services/common/rpc-atom-client.ts b/apps/web-foldkit/src/core/lib/services/common/rpc-atom-client.ts similarity index 100% rename from apps/web/src/lib/services/common/rpc-atom-client.ts rename to apps/web-foldkit/src/core/lib/services/common/rpc-atom-client.ts diff --git a/apps/web/src/lib/services/common/runtime.ts b/apps/web-foldkit/src/core/lib/services/common/runtime.ts similarity index 100% rename from apps/web/src/lib/services/common/runtime.ts rename to apps/web-foldkit/src/core/lib/services/common/runtime.ts diff --git a/apps/web/src/lib/services/common/telemetry.ts b/apps/web-foldkit/src/core/lib/services/common/telemetry.ts similarity index 100% rename from apps/web/src/lib/services/common/telemetry.ts rename to apps/web-foldkit/src/core/lib/services/common/telemetry.ts diff --git a/apps/web/src/lib/session-bearer.ts b/apps/web-foldkit/src/core/lib/session-bearer.ts similarity index 100% rename from apps/web/src/lib/session-bearer.ts rename to apps/web-foldkit/src/core/lib/session-bearer.ts diff --git a/apps/web/src/lib/tauri-autostart.ts b/apps/web-foldkit/src/core/lib/tauri-autostart.ts similarity index 100% rename from apps/web/src/lib/tauri-autostart.ts rename to apps/web-foldkit/src/core/lib/tauri-autostart.ts diff --git a/apps/web/src/lib/tauri.ts b/apps/web-foldkit/src/core/lib/tauri.ts similarity index 100% rename from apps/web/src/lib/tauri.ts rename to apps/web-foldkit/src/core/lib/tauri.ts diff --git a/apps/web/src/lib/theme/apply.ts b/apps/web-foldkit/src/core/lib/theme/apply.ts similarity index 100% rename from apps/web/src/lib/theme/apply.ts rename to apps/web-foldkit/src/core/lib/theme/apply.ts diff --git a/apps/web/src/lib/theme/presets.ts b/apps/web-foldkit/src/core/lib/theme/presets.ts similarity index 100% rename from apps/web/src/lib/theme/presets.ts rename to apps/web-foldkit/src/core/lib/theme/presets.ts diff --git a/apps/web/src/lib/theme/remix.ts b/apps/web-foldkit/src/core/lib/theme/remix.ts similarity index 100% rename from apps/web/src/lib/theme/remix.ts rename to apps/web-foldkit/src/core/lib/theme/remix.ts diff --git a/apps/web/src/lib/upload-to-storage.ts b/apps/web-foldkit/src/core/lib/upload-to-storage.ts similarity index 100% rename from apps/web/src/lib/upload-to-storage.ts rename to apps/web-foldkit/src/core/lib/upload-to-storage.ts diff --git a/apps/web/src/lib/utils.ts b/apps/web-foldkit/src/core/lib/utils.ts similarity index 100% rename from apps/web/src/lib/utils.ts rename to apps/web-foldkit/src/core/lib/utils.ts diff --git a/apps/web/src/styles/code-syntax.css b/apps/web-foldkit/src/core/styles/code-syntax.css similarity index 100% rename from apps/web/src/styles/code-syntax.css rename to apps/web-foldkit/src/core/styles/code-syntax.css diff --git a/apps/web/src/styles/styles.css b/apps/web-foldkit/src/core/styles/styles.css similarity index 99% rename from apps/web/src/styles/styles.css rename to apps/web-foldkit/src/core/styles/styles.css index ec2bd447a..709df7727 100644 --- a/apps/web/src/styles/styles.css +++ b/apps/web-foldkit/src/core/styles/styles.css @@ -3,8 +3,6 @@ @import "./theme.css"; -@source "../../../node_modules/@videojs/react/dist"; - @plugin 'tailwindcss-react-aria-components'; /* diff --git a/apps/web/src/styles/theme.css b/apps/web-foldkit/src/core/styles/theme.css similarity index 100% rename from apps/web/src/styles/theme.css rename to apps/web-foldkit/src/core/styles/theme.css diff --git a/apps/web/src/utils/attachment-url.ts b/apps/web-foldkit/src/core/utils/attachment-url.ts similarity index 100% rename from apps/web/src/utils/attachment-url.ts rename to apps/web-foldkit/src/core/utils/attachment-url.ts diff --git a/apps/web/src/utils/cx.ts b/apps/web-foldkit/src/core/utils/cx.ts similarity index 100% rename from apps/web/src/utils/cx.ts rename to apps/web-foldkit/src/core/utils/cx.ts diff --git a/apps/web/src/utils/file-utils.ts b/apps/web-foldkit/src/core/utils/file-utils.ts similarity index 100% rename from apps/web/src/utils/file-utils.ts rename to apps/web-foldkit/src/core/utils/file-utils.ts diff --git a/apps/web/src/utils/image-crop.ts b/apps/web-foldkit/src/core/utils/image-crop.ts similarity index 100% rename from apps/web/src/utils/image-crop.ts rename to apps/web-foldkit/src/core/utils/image-crop.ts diff --git a/apps/web/src/utils/presence.test.ts b/apps/web-foldkit/src/core/utils/presence.test.ts similarity index 100% rename from apps/web/src/utils/presence.test.ts rename to apps/web-foldkit/src/core/utils/presence.test.ts diff --git a/apps/web/src/utils/presence.ts b/apps/web-foldkit/src/core/utils/presence.ts similarity index 100% rename from apps/web/src/utils/presence.ts rename to apps/web-foldkit/src/core/utils/presence.ts diff --git a/apps/web/src/utils/status.ts b/apps/web-foldkit/src/core/utils/status.ts similarity index 100% rename from apps/web/src/utils/status.ts rename to apps/web-foldkit/src/core/utils/status.ts diff --git a/apps/web/src/utils/timezone.ts b/apps/web-foldkit/src/core/utils/timezone.ts similarity index 100% rename from apps/web/src/utils/timezone.ts rename to apps/web-foldkit/src/core/utils/timezone.ts diff --git a/apps/web-foldkit/src/icons/index.ts b/apps/web-foldkit/src/icons/index.ts index 1274b4a46..9929638ea 100644 --- a/apps/web-foldkit/src/icons/index.ts +++ b/apps/web-foldkit/src/icons/index.ts @@ -1,4 +1,4 @@ -// Generated by scripts/generate-icons.ts from apps/web/src/components/icons. Do not edit. +// Nucleo icons, originally generated from the React app's icon components. Edit by hand. import type { Html, HtmlBuilder } from "foldkit/html" export interface IconOptions { diff --git a/apps/web-foldkit/src/page/my-settings/appearance/illustrations.ts b/apps/web-foldkit/src/page/my-settings/appearance/illustrations.ts index fd997b239..c2971498d 100644 --- a/apps/web-foldkit/src/page/my-settings/appearance/illustrations.ts +++ b/apps/web-foldkit/src/page/my-settings/appearance/illustrations.ts @@ -1,4 +1,4 @@ -// Generated by scripts/generate-appearance-illustrations.ts from apps/web/src/components/modals/appearances.tsx. Do not edit. +// Originally generated from the React app's appearance illustrations. Edit by hand. import type { Html, HtmlBuilder } from "foldkit/html" const illustration = <Message>( diff --git a/apps/web-foldkit/src/styles.css b/apps/web-foldkit/src/styles.css index 53dfa4b47..fb5c96c90 100644 --- a/apps/web-foldkit/src/styles.css +++ b/apps/web-foldkit/src/styles.css @@ -1,10 +1,7 @@ -/* Same stylesheet as the legacy app: identical tokens, utilities and variants. */ -@import "../../web/src/styles/styles.css"; -@import "../../web/src/styles/code-syntax.css"; +@import "./core/styles/styles.css"; +@import "./core/styles/code-syntax.css"; -/* Scan both apps so the generated utilities are a superset of the legacy stylesheet. */ @source "./"; -@source "../../web/src"; /* React Aria injects this at runtime for every pressable; the ported controls carry the same attribute. */ @layer { diff --git a/apps/web-foldkit/src/ui/appearance-previews.ts b/apps/web-foldkit/src/ui/appearance-previews.ts index 57f14c4e5..8a0b6850d 100644 --- a/apps/web-foldkit/src/ui/appearance-previews.ts +++ b/apps/web-foldkit/src/ui/appearance-previews.ts @@ -1,4 +1,4 @@ -// Generated from apps/web/src/components/modals/appearances.tsx (renderToStaticMarkup + parity codegen). Do not edit. +// Originally generated from the React app's appearance illustrations. Edit by hand. import type { Html, HtmlBuilder } from "foldkit/html" /** `System` appearance preview. */ diff --git a/apps/web-foldkit/tsconfig.json b/apps/web-foldkit/tsconfig.json index d954b1f9b..66b1e5dbb 100644 --- a/apps/web-foldkit/tsconfig.json +++ b/apps/web-foldkit/tsconfig.json @@ -13,6 +13,6 @@ "jsx": "preserve", "exactOptionalPropertyTypes": false, "types": ["vite/client"], - "paths": { "~/*": ["../web/src/*"] } + "paths": { "~/*": ["./src/core/*"] } } } diff --git a/apps/web-foldkit/vite.config.ts b/apps/web-foldkit/vite.config.ts index c9767e3f1..abb2e4bfa 100644 --- a/apps/web-foldkit/vite.config.ts +++ b/apps/web-foldkit/vite.config.ts @@ -27,13 +27,11 @@ export default defineConfig({ target: process.env.TAURI_ENV_PLATFORM === "windows" ? "chrome105" : "safari14", sourcemap: !!process.env.TAURI_ENV_DEBUG, }, - publicDir: resolve(__dirname, "../web/public"), plugins: [tailwindcss(), foldkit({ devToolsMcpPort: false })], resolve: { alias: { - // Spike: reuse framework-free legacy modules (collections, theme, RPC client) in place. - // Phase 1 moves them into packages/web-core. - "~": resolve(__dirname, "../web/src"), + // Framework-free modules carried over from the React app (collections, theme, RPC client). + "~": resolve(__dirname, "src/core"), }, }, define: { diff --git a/apps/web/.cta.json b/apps/web/.cta.json deleted file mode 100644 index c34a62fb6..000000000 --- a/apps/web/.cta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "projectName": "web", - "mode": "file-router", - "typescript": true, - "tailwind": true, - "packageManager": "bun", - "git": true, - "version": 1, - "framework": "react-cra", - "chosenAddOns": ["shadcn"] -} diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index 5ae18f965..000000000 --- a/apps/web/.env.example +++ /dev/null @@ -1,26 +0,0 @@ -# Backend API URL -# Local backend service (Docker) -VITE_BACKEND_URL=http://localhost:3003 - -# Cluster Service URL -# Local cluster service (Docker) -VITE_CLUSTER_URL=http://localhost:3020 - -# Clerk Publishable Key -# Get this from: https://dashboard.clerk.com/ → API Keys -VITE_CLERK_PUBLISHABLE_KEY=pk_live_your_publishable_key_here - -# Electric SQL Proxy URL -# Points to Cloudflare Worker proxy (apps/proxyv2) -# The proxy forwards requests to Electric service -VITE_ELECTRIC_URL=http://localhost:8787/v1/shape - -# For production, use deployed Cloudflare Worker URL: -# VITE_ELECTRIC_URL=https://your-worker.your-domain.workers.dev/v1/shape - -# NOTE: Direct Electric connection (bypassing proxy): -# VITE_ELECTRIC_URL=http://localhost:5133/v1/shape - -# Cloudflare R2 Public URL -# Your CDN URL for public assets -VITE_R2_PUBLIC_URL=https://cdn.your-domain.com diff --git a/apps/web/.gitignore b/apps/web/.gitignore deleted file mode 100644 index e51879139..000000000 --- a/apps/web/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -.DS_Store -dist -.idea -dist-ssr -*.local -count.txt -.env -.nitro -.tanstack diff --git a/apps/web/README.md b/apps/web/README.md deleted file mode 100644 index 7c8677e04..000000000 --- a/apps/web/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Hazel Web App - -React frontend for Hazel Chat built with Vite, TanStack Router, and TailwindCSS v4. - -## Setup - -See the [root README](../../README.md) for development setup instructions. - -## Tech Stack - -- **React 19** with TypeScript -- **Vite** for fast development and builds -- **TanStack Router** with file-based routing (`src/routes/`) -- **TanStack DB** with Electric SQL for local-first real-time sync -- **TailwindCSS v4** for styling -- **React Aria Components** for accessible UI primitives - -## Development - -Once setup is complete, start all services from the monorepo root: - -```bash -bun run dev -``` - -The web app runs at http://localhost:3000. - -## Routing - -Routes are file-based in `src/routes/`. TanStack Router automatically generates route types. - -## Styling - -This project uses TailwindCSS v4 with Radix UI themes. Run `bun run format` to auto-format code. diff --git a/apps/web/TODO.md b/apps/web/TODO.md deleted file mode 100644 index 4bd2b1054..000000000 --- a/apps/web/TODO.md +++ /dev/null @@ -1,12 +0,0 @@ -- Webhook for invitation.created failing -- Image Viewer - -- Make all Icons use one Icon Lib -- Make Presence Work better and add Away Status - -- Better Chat Input - - Better Code Highlighting - - Mentions - - Slash Commands - -# Fixes diff --git a/apps/web/components.json b/apps/web/components.json deleted file mode 100644 index 407ba20c6..000000000 --- a/apps/web/components.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/styles/styles.css", - "baseColor": "zinc", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "~/components", - "utils": "~/lib/utils", - "ui": "~/components/ui", - "lib": "~/lib", - "hooks": "~/hooks" - }, - "iconLibrary": "@heroicons/react", - "registries": { - "@intentui": "https://intentui.com/r/{name}", - "@irsyadadl": { - "url": "https://design.intentui.com/r/{name}", - "headers": { - "Authorization": "Bearer ${REGISTRY_TOKEN}" - } - } - } -} diff --git a/apps/web/index.html b/apps/web/index.html deleted file mode 100644 index 90102eefe..000000000 --- a/apps/web/index.html +++ /dev/null @@ -1,17 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <link rel="icon" href="/icon.svg" /> - <meta name="theme-color" content="#000000" /> - <meta name="description" content="Slack alternative for modern teams." /> - <link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" /> - <link rel="manifest" href="/manifest.json" /> - <title>Hazel Chat - - -
- - - diff --git a/apps/web/package.json b/apps/web/package.json deleted file mode 100644 index 9397bec06..000000000 --- a/apps/web/package.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "name": "web", - "private": true, - "type": "module", - "scripts": { - "dev": "vite --port 3000", - "start": "vite --port 3000", - "build": "vite build && tsc", - "serve": "vite preview", - "test": "vitest run", - "typecheck": "tsc --noEmit", - "tauri": "bun run --cwd ../desktop tauri" - }, - "dependencies": { - "@clerk/react": "^6.4.3", - "@cloudflare/realtimekit-react": "^1.2.4", - "@cloudflare/realtimekit-react-ui": "^1.1.0", - "@effect/atom-react": "catalog:effect", - "@effect/opentelemetry": "catalog:effect", - "@effect/platform-browser": "catalog:effect", - "@electric-sql/client": "1.5.28", - "@fontsource/inter": "^5.2.8", - "@hazel/actors": "workspace:*", - "@hazel/backend": "workspace:*", - "@hazel/db": "workspace:*", - "@hazel/desktop": "workspace:*", - "@hazel/domain": "workspace:*", - "@hazel/effect-electric-db-collection": "workspace:*", - "@hazel/integrations": "workspace:*", - "@hazel/link-preview-worker": "workspace:*", - "@hazel/schema": "workspace:*", - "@hazel/tanstack-db-atom": "workspace:*", - "@heroicons/react": "^2.2.0", - "@internationalized/date": "^3.12.0", - "@legendapp/list": "3.0.0-beta.41", - "@paper-design/shaders-react": "^0.0.71", - "@posthog/react": "^1.8.2", - "@react-aria/i18n": "^3.12.16", - "@react-aria/optimize-locales-plugin": "^1.1.5", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.2.1", - "@tanstack/db": "0.11.3", - "@tanstack/devtools-event-client": "^0.4.1", - "@tanstack/devtools-vite": "^0.5.3", - "@tanstack/electric-db-collection": "0.5.4", - "@tanstack/query-db-collection": "1.3.4", - "@tanstack/react-db": "0.5.3", - "@tanstack/react-devtools": "^0.9.10", - "@tanstack/react-form": "^1.28.4", - "@tanstack/react-hotkeys": "0.4.0", - "@tanstack/react-query": "^5.90.21", - "@tanstack/react-router": "1.166.6", - "@tanstack/react-router-devtools": "1.166.6", - "@tanstack/router-plugin": "^1.166.6", - "@untitledui/file-icons": "^0.0.9", - "@videojs/react": "^10.0.0-beta.3", - "arktype": "^2.2.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "effect": "catalog:effect", - "effect-rpc-tanstack-devtools": "0.1.1", - "embla-carousel-react": "^8.6.0", - "facehash": "^0.1.0", - "frimousse": "^0.3.0", - "input-otp": "^1.4.2", - "marked": "^17.0.4", - "motion": "^12.35.2", - "next-themes": "^0.4.6", - "posthog-js": "^1.360.0", - "prismjs": "^1.30.0", - "react": "19.2.4", - "react-aria": "^3.47.0", - "react-aria-components": "^1.16.0", - "react-dom": "19.2.4", - "react-error-boundary": "^6.1.1", - "react-stately": "^3.45.0", - "react-tweet": "^3.3.0", - "remark-gfm": "^4.0.1", - "remark-math": "^6.0.0", - "remend": "^1.2.2", - "rivetkit": "2.1.6", - "slate": "0.123.0", - "slate-history": "^0.113.1", - "slate-react": "^0.123.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.5.0", - "tailwind-variants": "^3.2.2", - "tailwindcss": "^4.2.1", - "tailwindcss-animate": "^1.0.7", - "tailwindcss-react-aria-components": "^2.0.1", - "tw-animate-css": "^1.4.0", - "workbox-window": "^7.4.0" - }, - "devDependencies": { - "@react-types/overlays": "^3.9.4", - "@react-types/shared": "^3.33.1", - "@tailwindcss/postcss": "^4.2.1", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.2", - "@types/prismjs": "^1.26.6", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vite-pwa/assets-generator": "^1.0.2", - "@vitejs/plugin-react": "6.0.1", - "babel-plugin-react-compiler": "^19.1.0-rc.3", - "jsdom": "^28.1.0", - "postcss": "^8.5.8", - "sharp": "^0.34.5", - "typescript": "catalog:tooling", - "vite": "8.0.0", - "vite-bundle-analyzer": "^1.3.6", - "vite-plugin-pwa": "^1.2.0", - "vitest": "^4.1.0", - "web": "^0.0.2", - "web-vitals": "^5.1.0" - } -} diff --git a/apps/web/src/atoms/bot-atoms.ts b/apps/web/src/atoms/bot-atoms.ts deleted file mode 100644 index d9023bf1c..000000000 --- a/apps/web/src/atoms/bot-atoms.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { Bot, BotCommand } from "@hazel/domain/models" -import type { PublicBotInfo } from "@hazel/domain/rpc" -import type { Schema } from "effect" -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Type for bot data returned from RPC. - * Inferred from the domain model's JSON schema to stay in sync automatically. - */ -export type BotData = Schema.Schema.Type - -/** - * Type for bot command data returned from RPC. - */ -export type BotCommandData = Schema.Schema.Type - -/** - * Type for public bot data with install status. - */ -export type PublicBotData = PublicBotInfo - -// === Bot Management (for bot creators) === - -/** - * Mutation atom for creating a new bot. - * Returns the bot data including the plain token (only shown once). - */ -export const createBotMutation = HazelRpcClient.mutation("bot.create") - -/** - * Mutation atom for listing bots created by the user. - */ -export const listBotsMutation = HazelRpcClient.mutation("bot.list") - -/** - * Mutation atom for getting a single bot by ID. - */ -export const getBotMutation = HazelRpcClient.mutation("bot.get") - -/** - * Mutation atom for updating bot configuration. - */ -export const updateBotMutation = HazelRpcClient.mutation("bot.update") - -/** - * Mutation atom for deleting a bot. - */ -export const deleteBotMutation = HazelRpcClient.mutation("bot.delete") - -/** - * Mutation atom for regenerating a bot's API token. - * Returns the new token (only shown once). - */ -export const regenerateBotTokenMutation = HazelRpcClient.mutation("bot.regenerateToken") - -/** - * Mutation atom for listing commands registered by a bot. - */ -export const getBotCommandsMutation = HazelRpcClient.mutation("bot.getCommands") - -// === Bot Marketplace (for bot users) === - -/** - * Mutation atom for listing public bots in the marketplace. - * Returns bots with isInstalled flag and creator name. - */ -export const listPublicBotsMutation = HazelRpcClient.mutation("bot.listPublic") - -/** - * Mutation atom for listing bots installed in the organization. - */ -export const listInstalledBotsMutation = HazelRpcClient.mutation("bot.listInstalled") - -/** - * Mutation atom for installing a public bot. - */ -export const installBotMutation = HazelRpcClient.mutation("bot.install") - -/** - * Mutation atom for installing a bot by ID (public or private). - */ -export const installBotByIdMutation = HazelRpcClient.mutation("bot.installById") - -/** - * Mutation atom for uninstalling a bot. - */ -export const uninstallBotMutation = HazelRpcClient.mutation("bot.uninstall") - -/** - * Mutation atom for updating a bot's avatar. - */ -export const updateBotAvatarMutation = HazelRpcClient.mutation("bot.updateAvatar") diff --git a/apps/web/src/atoms/channel-atoms.ts b/apps/web/src/atoms/channel-atoms.ts deleted file mode 100644 index c4adeca97..000000000 --- a/apps/web/src/atoms/channel-atoms.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Mutation atom for creating DM/group channels - */ -export const createDmChannelMutation = HazelRpcClient.mutation("channel.createDm") - -/** - * Mutation atom for updating channels - */ -export const updateChannelMutation = HazelRpcClient.mutation("channel.update") - -/** - * Mutation atom for deleting channels - */ -export const deleteChannelMutation = HazelRpcClient.mutation("channel.delete") - -/** - * Mutation atom for generating AI thread names - * Triggers the ThreadNamingWorkflow to generate a descriptive name - */ -export const generateThreadNameMutation = HazelRpcClient.mutation("channel.generateName") diff --git a/apps/web/src/atoms/channel-member-atoms.ts b/apps/web/src/atoms/channel-member-atoms.ts deleted file mode 100644 index 1739a7de3..000000000 --- a/apps/web/src/atoms/channel-member-atoms.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Mutation atom for creating a channel member (joining a channel). - */ -export const createChannelMemberMutation = HazelRpcClient.mutation("channelMember.create") - -/** - * Mutation atom for updating channel member preferences. - * Use this for toggling mute, favorite, hidden status, etc. - */ -export const updateChannelMemberMutation = HazelRpcClient.mutation("channelMember.update") - -/** - * Mutation atom for deleting a channel member (leaving a channel). - */ -export const deleteChannelMemberMutation = HazelRpcClient.mutation("channelMember.delete") - -/** - * Mutation atom for clearing notification count when user views a channel. - * Called automatically when entering a channel to reset unread notifications. - */ -export const clearChannelNotificationsMutation = HazelRpcClient.mutation("channelMember.clearNotifications") diff --git a/apps/web/src/atoms/channel-webhook-atoms.ts b/apps/web/src/atoms/channel-webhook-atoms.ts deleted file mode 100644 index e335888b1..000000000 --- a/apps/web/src/atoms/channel-webhook-atoms.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ChannelWebhook } from "@hazel/domain/models" -import type { Schema } from "effect" -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Type for webhook data returned from RPC (without sensitive tokenHash field). - * Inferred from the domain model's JSON schema to stay in sync automatically. - */ -export type WebhookData = Schema.Schema.Type - -/** - * Mutation atom for creating a channel webhook. - * Returns the webhook data including the plain token (only shown once). - */ -export const createChannelWebhookMutation = HazelRpcClient.mutation("channelWebhook.create") - -/** - * Mutation atom for listing all webhooks for a channel. - */ -export const listChannelWebhooksMutation = HazelRpcClient.mutation("channelWebhook.list") - -/** - * Mutation atom for updating webhook configuration. - * Can update name, description, avatar URL, and enabled status. - */ -export const updateChannelWebhookMutation = HazelRpcClient.mutation("channelWebhook.update") - -/** - * Mutation atom for regenerating a webhook token. - * The old token is invalidated immediately. - * Returns the new token (only shown once). - */ -export const regenerateChannelWebhookTokenMutation = HazelRpcClient.mutation("channelWebhook.regenerateToken") - -/** - * Mutation atom for deleting a webhook (soft delete). - */ -export const deleteChannelWebhookMutation = HazelRpcClient.mutation("channelWebhook.delete") - -/** - * Mutation atom for listing all webhooks in the user's organization. - * Used by the integration settings page. - */ -export const listOrganizationWebhooksMutation = HazelRpcClient.mutation("channelWebhook.listByOrganization") diff --git a/apps/web/src/atoms/chat-atoms.ts b/apps/web/src/atoms/chat-atoms.ts deleted file mode 100644 index 6d7e26ca2..000000000 --- a/apps/web/src/atoms/chat-atoms.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Atom } from "effect/reactivity" -import type { AttachmentId, ChannelId, MessageId } from "@hazel/schema" - -/** - * Per-channel reply state using Atom.family - * Each channel gets its own isolated reply state - */ -export const replyToMessageAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make(null).pipe(Atom.keepAlive), -) - -/** - * Per-channel edit mode state - * Stores the MessageId being edited, or null if not editing - */ -export const editingMessageAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make(null).pipe(Atom.keepAlive), -) - -/** - * Global active thread channel ID - * Threads are app-wide, not per-channel - */ -export const activeThreadChannelIdAtom = Atom.make(null).pipe(Atom.keepAlive) - -/** - * Global active thread message ID - * Tracks which message the thread is for - */ -export const activeThreadMessageIdAtom = Atom.make(null).pipe(Atom.keepAlive) - -/** - * Per-channel scroll state tracking - * Tracks whether the user is currently at the bottom of the scroll container - * Managed by the useScrollToBottom hook via IntersectionObserver - */ -export const isAtBottomAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make(true).pipe(Atom.keepAlive), -) - -/** - * Per-channel uploaded attachments ready to be sent - * Stores AttachmentIds of files that have been uploaded and are ready to attach to a message - */ -export const uploadedAttachmentsAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make([]).pipe(Atom.keepAlive), -) - -/** - * Per-channel upload state - * Tracks whether files are currently being uploaded to prevent message sending - */ -export const isUploadingAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make(false).pipe(Atom.keepAlive), -) - -/** - * Per-channel uploading files - * Tracks files currently being uploaded with their metadata for display in composer - */ -export interface UploadingFile { - fileId: string - fileName: string - fileSize: number - progress: number -} - -export const uploadingFilesAtomFamily = Atom.family((_channelId: ChannelId) => - Atom.make([]).pipe(Atom.keepAlive), -) diff --git a/apps/web/src/atoms/chat-query-atoms.ts b/apps/web/src/atoms/chat-query-atoms.ts deleted file mode 100644 index 2932989af..000000000 --- a/apps/web/src/atoms/chat-query-atoms.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Atom } from "effect/reactivity" -import type { Message, PinnedMessage, User } from "@hazel/domain/models" -import type { ChannelId } from "@hazel/schema" -import { eq } from "@tanstack/db" -import { channelCollection } from "~/db/collections" -import { makeQuery } from "../../../../libs/tanstack-db-atom/src" - -export type MessageWithPinned = Message.Type & { - pinnedMessage: PinnedMessage.Type | null | undefined - author: User.Type | null | undefined - isSyncedFromDiscord?: boolean -} - -export type ProcessedMessage = { - message: MessageWithPinned - isGroupStart: boolean - isGroupEnd: boolean - isFirstNewMessage: boolean - isPinned: boolean -} - -/** - * Atom family for fetching a channel by ID - * Returns the channel as an array (matching TanStack DB query results) - */ -export const channelByIdAtomFamily = Atom.family((channelId: ChannelId) => - makeQuery((q) => - q - .from({ channel: channelCollection }) - .where(({ channel }) => eq(channel.id, channelId)) - .orderBy(({ channel }) => channel.createdAt, "desc") - .findOne(), - ), -) diff --git a/apps/web/src/atoms/command-palette-state.ts b/apps/web/src/atoms/command-palette-state.ts deleted file mode 100644 index 2a914b75c..000000000 --- a/apps/web/src/atoms/command-palette-state.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Atom } from "effect/reactivity" -import type { FilterType, SearchFilter } from "~/lib/search-filter-parser" - -/** - * Page types available in the command palette - */ -export type CommandPalettePageType = - | "home" - | "search" - | "status" - | "appearance" - | "create-channel" - | "join-channel" - -/** - * Page-specific state for each page type - * Uses discriminated union for type-safe state management - */ -export type PageState = - | HomePageState - | SearchPageState - | StatusPageState - | AppearancePageState - | CreateChannelPageState - | JoinChannelPageState - -export interface HomePageState { - type: "home" - inputValue: string -} - -export interface SearchPageState { - type: "search" - /** Text query (excludes filter syntax) */ - query: string - /** Full raw input including filter syntax */ - rawInput: string - /** Resolved filters with IDs */ - filters: SearchFilter[] - /** Filter type currently being typed (for autocomplete) */ - activeFilterType: FilterType | null - /** Partial value being typed for active filter */ - activeFilterPartial: string - /** Selected result index for keyboard navigation */ - selectedIndex: number -} - -export interface StatusPageState { - type: "status" - inputValue: string -} - -export interface AppearancePageState { - type: "appearance" - inputValue: string -} - -export interface CreateChannelPageState { - type: "create-channel" - name: string - channelType: "public" | "private" - error: string | null - isSubmitting: boolean -} - -export interface JoinChannelPageState { - type: "join-channel" - searchQuery: string -} - -/** - * History entry that preserves page state for back navigation - */ -export interface HistoryEntry { - pageState: PageState -} - -/** - * Full command palette state - */ -export interface CommandPaletteState { - isOpen: boolean - currentPage: PageState - pageHistory: HistoryEntry[] -} - -/** - * Initial state for each page type - */ -export const initialPageStates: Record = { - home: { type: "home", inputValue: "" }, - search: { - type: "search", - query: "", - rawInput: "", - filters: [], - activeFilterType: null, - activeFilterPartial: "", - selectedIndex: 0, - }, - status: { type: "status", inputValue: "" }, - appearance: { type: "appearance", inputValue: "" }, - "create-channel": { - type: "create-channel", - name: "", - channelType: "public", - error: null, - isSubmitting: false, - }, - "join-channel": { - type: "join-channel", - searchQuery: "", - }, -} - -/** - * Initial command palette state - */ -export const initialCommandPaletteState: CommandPaletteState = { - isOpen: false, - currentPage: initialPageStates.home, - pageHistory: [], -} - -/** - * Check if a page is a form page (vs a list page) - * Form pages don't use the Autocomplete search input and render outside CommandMenuList - * Note: "search" page has its own custom search input, so it's also treated as a form page - */ -export const isFormPage = (pageType: CommandPalettePageType): boolean => { - return pageType === "create-channel" || pageType === "join-channel" || pageType === "search" -} - -/** - * Get the page type from a page state - */ -export const getPageType = (pageState: PageState): CommandPalettePageType => { - return pageState.type -} - -/** - * Main command palette state atom - */ -export const commandPaletteStateAtom = Atom.make(initialCommandPaletteState).pipe( - Atom.keepAlive, -) - -/** - * Derived atom for checking if we can go back in navigation - */ -export const canGoBackAtom = Atom.make((get) => { - const state = get(commandPaletteStateAtom) - return state.pageHistory.length > 0 -}).pipe(Atom.keepAlive) - -/** - * Derived atom for current page type - */ -export const currentPageTypeAtom = Atom.make((get) => { - const state = get(commandPaletteStateAtom) - return getPageType(state.currentPage) -}).pipe(Atom.keepAlive) - -/** - * Derived atom for checking if current page is a form page - */ -export const isCurrentPageFormAtom = Atom.make((get) => { - const pageType = get(currentPageTypeAtom) - return isFormPage(pageType) -}).pipe(Atom.keepAlive) diff --git a/apps/web/src/atoms/connect-share-atoms.ts b/apps/web/src/atoms/connect-share-atoms.ts deleted file mode 100644 index db5339d52..000000000 --- a/apps/web/src/atoms/connect-share-atoms.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { OrganizationId } from "@hazel/schema" -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -export const listIncomingInvitesQuery = (organizationId: OrganizationId) => - HazelRpcClient.query( - "connectShare.invite.listIncoming", - { organizationId }, - { - reactivityKeys: [`connectInvites:incoming:${organizationId}`], - }, - ) -export const listOutgoingInvitesQuery = (organizationId: OrganizationId) => - HazelRpcClient.query( - "connectShare.invite.listOutgoing", - { organizationId }, - { - reactivityKeys: [`connectInvites:outgoing:${organizationId}`], - }, - ) - -export const workspaceSearchMutation = HazelRpcClient.mutation("connectShare.workspace.search") -export const createConnectInviteMutation = HazelRpcClient.mutation("connectShare.invite.create") -export const acceptConnectInviteMutation = HazelRpcClient.mutation("connectShare.invite.accept") -export const declineConnectInviteMutation = HazelRpcClient.mutation("connectShare.invite.decline") -export const revokeConnectInviteMutation = HazelRpcClient.mutation("connectShare.invite.revoke") -export const disconnectConnectOrgMutation = HazelRpcClient.mutation("connectShare.organization.disconnect") -export const updateConnectSettingsMutation = HazelRpcClient.mutation("connectShare.settings.update") diff --git a/apps/web/src/atoms/custom-emoji-atoms.ts b/apps/web/src/atoms/custom-emoji-atoms.ts deleted file mode 100644 index 1c8d72ba2..000000000 --- a/apps/web/src/atoms/custom-emoji-atoms.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Atom, AsyncResult } from "effect/reactivity" -import type { OrganizationId } from "@hazel/schema" -import { and, eq, isNull } from "@tanstack/db" -import { customEmojiCollection } from "~/db/collections" -import { formatCustomEmojiKey } from "~/lib/custom-emoji-utils" -import { makeQuery } from "../../../../libs/tanstack-db-atom/src" - -/** - * Atom family for fetching all non-deleted custom emojis for an organization. - * Automatically deduplicates across components. - */ -export const customEmojisForOrgAtomFamily = Atom.family((orgId: OrganizationId) => - makeQuery((q) => - q - .from({ emoji: customEmojiCollection }) - .where((q) => and(eq(q.emoji.organizationId, orgId), isNull(q.emoji.deletedAt))), - ), -) - -/** - * Derived atom that builds a Map<"custom:name", { name, imageUrl }> for fast lookup. - * Used by reaction rendering and emoji resolution. - */ -export const customEmojiMapAtomFamily = Atom.family((orgId: OrganizationId) => - Atom.make((get) => { - const emojisResult = get(customEmojisForOrgAtomFamily(orgId)) - const emojis = AsyncResult.getOrElse(emojisResult, () => []) - - const map = new Map() - for (const emoji of emojis) { - map.set(formatCustomEmojiKey(emoji.name), { name: emoji.name, imageUrl: emoji.imageUrl }) - } - return map - }), -) diff --git a/apps/web/src/atoms/feature-discovery-atoms.ts b/apps/web/src/atoms/feature-discovery-atoms.ts deleted file mode 100644 index 266d783c8..000000000 --- a/apps/web/src/atoms/feature-discovery-atoms.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Atom } from "effect/reactivity" -import { useAtomSet, useAtomValue } from "@effect/atom-react" -import { Schema } from "effect" -import { useCallback } from "react" -import { platformStorageRuntime } from "~/lib/platform-storage" - -const HINT_IDS = ["command-palette", "create-channel"] as const - -export type HintId = (typeof HINT_IDS)[number] - -const DismissedHintsSchema = Schema.Record(Schema.String, Schema.Boolean) - -export const dismissedHintsAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "hazel-dismissed-hints", - schema: Schema.toCodecIso(DismissedHintsSchema), - defaultValue: () => ({}) as Record, -}).pipe(Atom.keepAlive) - -export function useFeatureHint(hintId: HintId) { - const dismissed = useAtomValue(dismissedHintsAtom) - const setDismissed = useAtomSet(dismissedHintsAtom) - - const isDismissed = dismissed[hintId] === true - - const dismiss = useCallback(() => { - setDismissed((prev) => ({ ...prev, [hintId]: true })) - }, [hintId, setDismissed]) - - return { isDismissed, dismiss, shouldShow: !isDismissed } -} diff --git a/apps/web/src/atoms/github-subscription-atoms.ts b/apps/web/src/atoms/github-subscription-atoms.ts deleted file mode 100644 index 489c63b68..000000000 --- a/apps/web/src/atoms/github-subscription-atoms.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { GitHubSubscription } from "@hazel/domain/models" -import type { Schema } from "effect" -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Type for GitHub subscription data returned from RPC. - * Inferred from the domain model's JSON schema to stay in sync automatically. - */ -export type GitHubSubscriptionData = Schema.Schema.Type - -/** - * Mutation atom for creating a GitHub subscription. - * Subscribes a channel to a GitHub repository. - */ -export const createGitHubSubscriptionMutation = HazelRpcClient.mutation("githubSubscription.create") - -/** - * Mutation atom for listing GitHub subscriptions for a channel. - */ -export const listGitHubSubscriptionsMutation = HazelRpcClient.mutation("githubSubscription.list") - -/** - * Mutation atom for listing all GitHub subscriptions for an organization. - * Used by the organization-level integration settings page. - */ -export const listOrganizationGitHubSubscriptionsMutation = HazelRpcClient.mutation( - "githubSubscription.listByOrganization", -) - -/** - * Mutation atom for updating a GitHub subscription. - * Can update enabled events, branch filter, and enabled status. - */ -export const updateGitHubSubscriptionMutation = HazelRpcClient.mutation("githubSubscription.update") - -/** - * Mutation atom for deleting a GitHub subscription (soft delete). - */ -export const deleteGitHubSubscriptionMutation = HazelRpcClient.mutation("githubSubscription.delete") diff --git a/apps/web/src/atoms/hotkey-atoms.test.ts b/apps/web/src/atoms/hotkey-atoms.test.ts deleted file mode 100644 index 2b432e448..000000000 --- a/apps/web/src/atoms/hotkey-atoms.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { normalizeHotkey } from "@tanstack/react-hotkeys" -import { describe, expect, it } from "vitest" -import { HOTKEY_DEFINITIONS_BY_ID } from "~/lib/hotkeys/hotkey-registry" -import { resolveHotkeys, sanitizeHotkeyOverrides } from "./hotkey-atoms" - -describe("hotkey-atoms", () => { - it("uses default hotkeys when no overrides are present", () => { - const resolvedHotkeys = resolveHotkeys({}) - expect(resolvedHotkeys["commandPalette.open"]).toBe( - HOTKEY_DEFINITIONS_BY_ID["commandPalette.open"].defaultHotkey, - ) - expect(resolvedHotkeys["sidebar.toggle"]).toBe( - HOTKEY_DEFINITIONS_BY_ID["sidebar.toggle"].defaultHotkey, - ) - }) - - it("applies valid overrides", () => { - const override = "Ctrl+Shift+P" - const resolvedHotkeys = resolveHotkeys({ - "search.open": override, - }) - - expect(resolvedHotkeys["search.open"]).toBe(normalizeHotkey(override)) - }) - - it("ignores invalid hotkey overrides", () => { - const defaultHotkey = HOTKEY_DEFINITIONS_BY_ID["search.open"].defaultHotkey - const resolvedHotkeys = resolveHotkeys({ - "search.open": "Definitely+Invalid+Key", - }) - - expect(resolvedHotkeys["search.open"]).toBe(defaultHotkey) - }) - - it("ignores unknown action ids", () => { - const overrides = sanitizeHotkeyOverrides({ - "commandPalette.open": "Mod+P", - "unknown.action": "Mod+Q", - }) - - expect(overrides["commandPalette.open"]).toBe(normalizeHotkey("Mod+P")) - expect("unknown.action" in overrides).toBe(false) - }) -}) diff --git a/apps/web/src/atoms/hotkey-atoms.ts b/apps/web/src/atoms/hotkey-atoms.ts deleted file mode 100644 index 5a926b9de..000000000 --- a/apps/web/src/atoms/hotkey-atoms.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { Atom } from "effect/reactivity" -import { useAtomSet, useAtomValue } from "@effect/atom-react" -import { normalizeHotkey, validateHotkey, type Hotkey } from "@tanstack/react-hotkeys" -import { Schema } from "effect" -import { useCallback } from "react" -import type { AppHotkeyDefinition } from "~/lib/hotkeys/hotkey-registry" -import { - HOTKEY_DEFINITIONS, - HOTKEY_DEFINITIONS_BY_ID, - isAppHotkeyActionId, - type AppHotkeyActionId, -} from "~/lib/hotkeys/hotkey-registry" -import { platformStorageRuntime } from "~/lib/platform-storage" - -const HotkeyOverridesSchema = Schema.Record(Schema.String, Schema.String) - -type HotkeyOverrides = typeof HotkeyOverridesSchema.Type - -const DEFAULT_HOTKEYS = HOTKEY_DEFINITIONS.reduce( - (accumulator, definition) => { - accumulator[definition.id as AppHotkeyActionId] = definition.defaultHotkey - return accumulator - }, - {} as Record, -) - -export interface ResolvedHotkeyDefinition extends AppHotkeyDefinition { - id: AppHotkeyActionId - resolvedHotkey: Hotkey - isOverridden: boolean -} - -export const hotkeyOverridesAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "hazel-hotkey-overrides", - schema: Schema.toCodecIso(HotkeyOverridesSchema), - defaultValue: () => ({}) as HotkeyOverrides, -}).pipe(Atom.keepAlive) - -const toValidHotkey = (hotkey: string): Hotkey | null => { - const validationResult = validateHotkey(hotkey) - if (!validationResult.valid) return null - return normalizeHotkey(hotkey) as Hotkey -} - -export function sanitizeHotkeyOverrides( - rawOverrides: Record | null | undefined, -): Partial> { - if (!rawOverrides) return {} - - const sanitized: Partial> = {} - for (const [actionId, rawHotkey] of Object.entries(rawOverrides)) { - if (!isAppHotkeyActionId(actionId)) continue - const validHotkey = toValidHotkey(rawHotkey) - if (!validHotkey) continue - sanitized[actionId] = validHotkey - } - - return sanitized -} - -export function resolveHotkeys( - rawOverrides: Record | null | undefined, -): Record { - const sanitizedOverrides = sanitizeHotkeyOverrides(rawOverrides) - const resolved = { ...DEFAULT_HOTKEYS } - - for (const [actionId, hotkey] of Object.entries(sanitizedOverrides)) { - resolved[actionId as AppHotkeyActionId] = hotkey - } - - return resolved -} - -export const resolvedHotkeysAtom = Atom.make((get) => { - return resolveHotkeys(get(hotkeyOverridesAtom)) -}).pipe(Atom.keepAlive) - -export const resolvedHotkeysCatalogAtom = Atom.make((get) => { - const resolvedHotkeys = get(resolvedHotkeysAtom) - const sanitizedOverrides = sanitizeHotkeyOverrides(get(hotkeyOverridesAtom)) - - return HOTKEY_DEFINITIONS.map((definition) => { - const actionId = definition.id as AppHotkeyActionId - const resolvedHotkey = resolvedHotkeys[actionId] - const isOverridden = sanitizedOverrides[actionId] !== undefined - - return { - ...definition, - id: actionId, - resolvedHotkey, - isOverridden, - } - }) as ReadonlyArray -}).pipe(Atom.keepAlive) - -export const useResolvedHotkey = (actionId: AppHotkeyActionId): Hotkey => { - const resolvedHotkeys = useAtomValue(resolvedHotkeysAtom) - return resolvedHotkeys[actionId] ?? HOTKEY_DEFINITIONS_BY_ID[actionId].defaultHotkey -} - -export const useResolvedHotkeysCatalog = (): ReadonlyArray => { - return useAtomValue(resolvedHotkeysCatalogAtom) -} - -export const useSetHotkeyOverride = () => { - const setHotkeyOverrides = useAtomSet(hotkeyOverridesAtom) - - return useCallback( - (actionId: AppHotkeyActionId, hotkey: string): boolean => { - const validHotkey = toValidHotkey(hotkey) - if (!validHotkey) return false - - setHotkeyOverrides((overrides) => ({ - ...overrides, - [actionId]: validHotkey, - })) - return true - }, - [setHotkeyOverrides], - ) -} - -export const useResetHotkeyOverride = () => { - const setHotkeyOverrides = useAtomSet(hotkeyOverridesAtom) - - return useCallback( - (actionId: AppHotkeyActionId) => { - setHotkeyOverrides((overrides) => { - if (!(actionId in overrides)) return overrides - const nextOverrides = { ...overrides } - delete nextOverrides[actionId] - return nextOverrides - }) - }, - [setHotkeyOverrides], - ) -} - -export const useResetAllHotkeyOverrides = () => { - const setHotkeyOverrides = useAtomSet(hotkeyOverridesAtom) - return useCallback(() => setHotkeyOverrides(() => ({})), [setHotkeyOverrides]) -} diff --git a/apps/web/src/atoms/loading-state-atoms.ts b/apps/web/src/atoms/loading-state-atoms.ts deleted file mode 100644 index 1c3a17b49..000000000 --- a/apps/web/src/atoms/loading-state-atoms.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Atom } from "effect/reactivity" - -/** - * Loading state enum - */ -export type LoadingState = "idle" | "loading-top" | "loading-bottom" | "cooldown" - -/** - * Cooldown duration in milliseconds - */ -const COOLDOWN_MS = 300 - -/** - * Atom family for per-component loading states - * Each component can have its own isolated loading state - */ -export const loadingStateAtomFamily = Atom.family((_id: string) => - Atom.make("idle").pipe(Atom.keepAlive), -) - -/** - * Derived atom that checks if loading can start from top - */ -export const canLoadTopAtomFamily = Atom.family((id: string) => - Atom.make((get) => { - const state = get(loadingStateAtomFamily(id)) - return state === "idle" - }).pipe(Atom.keepAlive), -) - -/** - * Derived atom that checks if loading can start from bottom - */ -export const canLoadBottomAtomFamily = Atom.family((id: string) => - Atom.make((get) => { - const state = get(loadingStateAtomFamily(id)) - return state === "idle" - }).pipe(Atom.keepAlive), -) - -/** - * Derived atom that checks if currently loading - */ -export const isLoadingAtomFamily = Atom.family((id: string) => - Atom.make((get) => { - const state = get(loadingStateAtomFamily(id)) - return state === "loading-top" || state === "loading-bottom" - }).pipe(Atom.keepAlive), -) - -/** - * Helper function to start loading from top - * Returns true if loading started, false if already loading - */ -export const startLoadingTop = (id: string): boolean => { - let started = false - Atom.batch(() => { - return Atom.update(loadingStateAtomFamily(id), (currentState) => { - if (currentState === "idle") { - started = true - return "loading-top" - } - return currentState - }) - }) - return started -} - -/** - * Helper function to start loading from bottom - * Returns true if loading started, false if already loading - */ -export const startLoadingBottom = (id: string): boolean => { - let started = false - Atom.batch(() => { - return Atom.update(loadingStateAtomFamily(id), (currentState) => { - if (currentState === "idle") { - started = true - return "loading-bottom" - } - return currentState - }) - }) - return started -} - -/** - * Helper function to finish loading and enter cooldown - * Automatically returns to idle after cooldown period - */ -export const finishLoading = (id: string): void => { - Atom.batch(() => { - return Atom.set(loadingStateAtomFamily(id), "cooldown") - }) - - // Set timeout to return to idle after cooldown - setTimeout(() => { - Atom.batch(() => { - return Atom.set(loadingStateAtomFamily(id), "idle") - }) - }, COOLDOWN_MS) -} - -/** - * Helper function to reset loading state to idle - * Useful for cleanup or error handling - */ -export const resetLoadingState = (id: string): void => { - Atom.batch(() => { - return Atom.set(loadingStateAtomFamily(id), "idle") - }) -} diff --git a/apps/web/src/atoms/message-atoms.ts b/apps/web/src/atoms/message-atoms.ts deleted file mode 100644 index f01d767bb..000000000 --- a/apps/web/src/atoms/message-atoms.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Atom, AsyncResult } from "effect/reactivity" -import type { ChannelId, MessageId, UserId } from "@hazel/schema" -import { and, count, eq, isNull } from "@tanstack/db" -import { - chatSyncChannelLinkCollection, - chatSyncConnectionCollection, - chatSyncMessageLinkCollection, - messageCollection, - messageReactionCollection, - userCollection, - userPresenceStatusCollection, -} from "~/db/collections" -import { makeQuery } from "../../../../libs/tanstack-db-atom/src" - -/** - * Atom family for fetching reactions for a specific message - * Automatically deduplicates - if multiple MessageItems show the same message, - * only one query is executed - */ -export const messageReactionsAtomFamily = Atom.family((messageId: MessageId) => - makeQuery((q) => - q.from({ reactions: messageReactionCollection }).where((q) => eq(q.reactions.messageId, messageId)), - ), -) - -/** - * Derived atom that processes raw reactions into aggregated format - * Computation is memoized and only runs when reactions change - * - * Uses string key format `${messageId}:${currentUserId}` to prevent - * atom re-subscription from object key reference changes - */ -export const processedReactionsAtomFamily = Atom.family((key: `${MessageId}:${string}`) => - Atom.make((get) => { - const [messageId] = key.split(":") as [MessageId, string] - const currentUserId = key.slice(messageId.length + 1) // Handle userId that may contain ":" - const reactionsResult = get(messageReactionsAtomFamily(messageId)) - const reactions = AsyncResult.getOrElse(reactionsResult, () => []) - - // Aggregate reactions by emoji - return Object.entries( - reactions.reduce( - (acc, reaction) => { - if (!acc[reaction.emoji]) { - acc[reaction.emoji] = { - count: 0, - users: [], - hasReacted: false, - } - } - acc[reaction.emoji]!.count++ - acc[reaction.emoji]!.users.push(reaction.userId) - if (reaction.userId === currentUserId) { - acc[reaction.emoji]!.hasReacted = true - } - return acc - }, - {} as Record, - ), - ) - }), -) - -/** - * Atom family for fetching user data with presence status - * Automatically deduplicates - if the same user appears in multiple messages, - * only one query is executed and the result is shared - */ -export const userWithPresenceAtomFamily = Atom.family((userId: UserId) => - makeQuery((q) => - q - .from({ user: userCollection }) - .leftJoin({ presence: userPresenceStatusCollection }, ({ user, presence }) => - eq(user.id, presence.userId), - ) - .where((q) => eq(q.user.id, userId)) - .select(({ user, presence }) => ({ user, presence })), - ), -) - -/** - * Atom family for fetching a message with its author - * Used primarily for reply sections - */ -export const messageWithAuthorAtomFamily = Atom.family((messageId: MessageId) => - makeQuery((q) => - q - .from({ message: messageCollection }) - .innerJoin({ author: userCollection }, ({ message, author }) => eq(message.authorId, author.id)) - .where((q) => eq(q.message.id, messageId)) - .findOne() - .select(({ message, author }) => ({ ...message, author: author })) - .orderBy((q) => q.message.createdAt, "desc"), - ), -) - -/** - * Atom family to check whether a Hazel message originated from Discord external->Hazel sync. - */ -export const isDiscordSyncedMessageAtomFamily = Atom.family((messageId: MessageId) => - makeQuery((q) => - q - .from({ messageLink: chatSyncMessageLinkCollection }) - .innerJoin({ channelLink: chatSyncChannelLinkCollection }, ({ messageLink, channelLink }) => - eq(messageLink.channelLinkId, channelLink.id), - ) - .innerJoin({ connection: chatSyncConnectionCollection }, ({ channelLink, connection }) => - eq(channelLink.syncConnectionId, connection.id), - ) - .where(({ messageLink, channelLink, connection }) => - and( - eq(messageLink.hazelMessageId, messageId), - eq(messageLink.source, "external"), - eq(connection.provider, "discord"), - isNull(messageLink.deletedAt), - isNull(channelLink.deletedAt), - isNull(connection.deletedAt), - ), - ) - .orderBy(({ messageLink }) => messageLink.createdAt, "desc") - .limit(1), - ), -) - -/** - * Atom family for fetching thread messages by channel ID - * Returns messages in chronological order - */ -export const threadMessagesAtomFamily = Atom.family( - ({ threadChannelId, maxPreviewMessages }: { threadChannelId: ChannelId; maxPreviewMessages: number }) => - makeQuery( - (q) => - q - .from({ message: messageCollection }) - .where(({ message }) => eq(message.channelId, threadChannelId)) - .orderBy(({ message }) => message.createdAt, "asc") - .limit(maxPreviewMessages + 1), // Fetch one extra to check if there are more - ), -) - -/** - * Atom family for counting total messages in a thread - */ -export const threadMessageCountAtomFamily = Atom.family((threadChannelId: ChannelId) => - makeQuery((q) => - q - .from({ message: messageCollection }) - .where(({ message }) => eq(message.channelId, threadChannelId)) - .select(({ message }) => ({ - count: count(message.id), - })), - ), -) - -/** - * Atom family for fetching thread messages with their authors - * Returns messages in chronological order with author data joined - * Used by ThreadMessageItem to display full message features - */ -export const threadMessagesWithAuthorAtomFamily = Atom.family( - ({ threadChannelId }: { threadChannelId: ChannelId }) => - makeQuery((q) => - q - .from({ message: messageCollection }) - .leftJoin({ author: userCollection }, ({ message, author }) => - eq(message.authorId, author.id), - ) - .where(({ message }) => eq(message.channelId, threadChannelId)) - .select(({ message, author }) => ({ - ...message, - author, - pinnedMessage: null, - })) - .orderBy(({ message }) => message.createdAt, "asc"), - ), -) diff --git a/apps/web/src/atoms/modal-atoms.ts b/apps/web/src/atoms/modal-atoms.ts deleted file mode 100644 index b65193ab7..000000000 --- a/apps/web/src/atoms/modal-atoms.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Atom } from "effect/reactivity" -import { useAtomSet, useAtomValue } from "@effect/atom-react" -import { useCallback } from "react" - -/** - * Supported modal types in the application - * Single source of truth - add new modals here - */ -const MODAL_TYPES = [ - "create-dm", - "new-channel", - "join-channel", - "rename-channel", - "change-role", - "email-invite", - "create-organization", - "create-section", - "command-palette", - "delete-channel", -] as const - -export type ModalType = (typeof MODAL_TYPES)[number] - -/** - * Modal state interface - */ -interface ModalState { - type: ModalType - isOpen: boolean - metadata?: Record -} - -/** - * Atom family for managing individual modal states - * Each modal type gets its own isolated state - */ -export const modalAtomFamily = Atom.family((type: ModalType) => - Atom.make({ - type, - isOpen: false, - metadata: undefined, - }).pipe(Atom.keepAlive), -) - -/** - * Derived atom that tracks all open modals - * Useful for modal stacking and z-index management - */ -export const openModalsAtom = Atom.make((get) => { - return MODAL_TYPES.map((type) => get(modalAtomFamily(type))) - .filter((modal) => modal.isOpen) - .map((modal) => modal.type) -}).pipe(Atom.keepAlive) - -/** - * React hook for modal state and actions - * Use this in React components to properly trigger re-renders - * - * @example - * const { isOpen, open, close } = useModal("new-channel") - * - * !open && close()}>... - */ -export const useModal = (type: ModalType) => { - const state = useAtomValue(modalAtomFamily(type)) - const setState = useAtomSet(modalAtomFamily(type)) - - const open = useCallback( - (metadata?: Record) => { - setState((prev) => ({ ...prev, isOpen: true, metadata })) - }, - [setState], - ) - - const close = useCallback(() => { - setState((prev) => ({ ...prev, isOpen: false, metadata: undefined })) - }, [setState]) - - return { - isOpen: state.isOpen, - metadata: state.metadata, - open, - close, - } -} diff --git a/apps/web/src/atoms/onboarding-atoms.ts b/apps/web/src/atoms/onboarding-atoms.ts deleted file mode 100644 index 14bba7263..000000000 --- a/apps/web/src/atoms/onboarding-atoms.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Atom } from "effect/reactivity" -import type { OrganizationId } from "@hazel/schema" - -// Step identifiers. Clerk handles org creation during sign-up via its -// "Force organization creation" dashboard setting — our custom flow -// just needs to know about (not create) the org. -export type OnboardingStep = - | "welcome" - | "profileInfo" - | "timezoneSelection" - | "themeSelection" - | "useCases" - | "role" - | "teamInvitation" - | "finalization" - | "completed" - -// User type determines which flow to follow -export type UserType = "creator" | "invited" - -// Accumulated wizard data -export interface OnboardingData { - firstName?: string - lastName?: string - timezone?: string - theme?: "dark" | "light" | "system" - brandColor?: string - orgName?: string - orgSlug?: string - createdOrgId?: OrganizationId - useCases: string[] - role?: string - emails: string[] -} - -// Complete wizard state -export interface OnboardingState { - currentStep: OnboardingStep - direction: "forward" | "backward" - userType: UserType - data: OnboardingData - isProcessing: boolean - error?: string - initialOrgId?: OrganizationId - initialOrganization?: { - id: OrganizationId - name?: string - slug?: string - } -} - -// Flow configuration (exported for URL validation) -export const CREATOR_FLOW: OnboardingStep[] = [ - "welcome", - "profileInfo", - "timezoneSelection", - "themeSelection", - "useCases", - "role", - "teamInvitation", - "finalization", -] - -export const INVITED_FLOW: OnboardingStep[] = [ - "welcome", - "profileInfo", - "timezoneSelection", - "themeSelection", - "role", - "finalization", -] - -// Validate if a step is valid for a given user type -export function isValidStepForUser(step: string, userType: UserType): step is OnboardingStep { - const flow = userType === "creator" ? CREATOR_FLOW : INVITED_FLOW - return flow.includes(step as OnboardingStep) -} - -// Step numbers for progress indicator -const STEP_NUMBERS: Record = { - welcome: { creator: 1, invited: 1 }, - profileInfo: { creator: 2, invited: 2 }, - timezoneSelection: { creator: 3, invited: 3 }, - themeSelection: { creator: 4, invited: 4 }, - useCases: { creator: 5, invited: null }, - role: { creator: 6, invited: 5 }, - teamInvitation: { creator: 7, invited: null }, - finalization: { creator: 7, invited: 5 }, - completed: { creator: 7, invited: 5 }, -} - -// Helper functions -function getFlow(userType: UserType): OnboardingStep[] { - return userType === "creator" ? CREATOR_FLOW : INVITED_FLOW -} - -export function getNextStep(currentStep: OnboardingStep, userType: UserType): OnboardingStep | undefined { - const flow = getFlow(userType) - const currentIndex = flow.indexOf(currentStep) - if (currentIndex === -1 || currentIndex >= flow.length - 1) return undefined - return flow[currentIndex + 1] -} - -export function getPreviousStep(currentStep: OnboardingStep, userType: UserType): OnboardingStep | undefined { - const flow = getFlow(userType) - const currentIndex = flow.indexOf(currentStep) - if (currentIndex <= 0) return undefined - return flow[currentIndex - 1] -} - -export function getStepNumber(step: OnboardingStep, userType: UserType): number { - const numbers = STEP_NUMBERS[step] - return (userType === "creator" ? numbers.creator : numbers.invited) ?? 1 -} - -export function getTotalSteps(userType: UserType): number { - return userType === "creator" ? 7 : 5 -} - -// Factory function to create initial state -export function createInitialState(input: { - orgId?: OrganizationId - organization?: { id: OrganizationId; name?: string; slug?: string } -}): OnboardingState { - const userType: UserType = input.orgId && input.organization?.slug ? "invited" : "creator" - - return { - currentStep: "welcome", - direction: "forward", - userType, - data: { - useCases: [], - emails: [], - }, - isProcessing: false, - error: undefined, - initialOrgId: input.orgId, - initialOrganization: input.organization, - } -} - -// Atom family keyed by a stable identifier for the onboarding session -export const onboardingAtomFamily = Atom.family((_key: string) => - Atom.make(createInitialState({})), -) - -// Derived atom helpers (use with the state directly in the hook) -export function computeStepNumber(state: OnboardingState): number { - return getStepNumber(state.currentStep, state.userType) -} - -export function computeTotalSteps(state: OnboardingState): number { - return getTotalSteps(state.userType) -} diff --git a/apps/web/src/atoms/organization-atoms.ts b/apps/web/src/atoms/organization-atoms.ts deleted file mode 100644 index 756e307c3..000000000 --- a/apps/web/src/atoms/organization-atoms.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Mutation atom for creating organizations - */ -export const createOrganizationMutation = HazelRpcClient.mutation("organization.create") - -/** - * Mutation atom for updating organizations - */ -export const updateOrganizationMutation = HazelRpcClient.mutation("organization.update") - -/** - * Mutation atom for deleting organizations - */ -export const deleteOrganizationMutation = HazelRpcClient.mutation("organization.delete") - -/** - * Mutation atom for setting organization slug - */ -export const setOrganizationSlugMutation = HazelRpcClient.mutation("organization.setSlug") - -/** - * Mutation atom for updating organization member metadata - */ -export const updateOrganizationMemberMetadataMutation = HazelRpcClient.mutation( - "organizationMember.updateMetadata", -) - -/** - * Mutation atom for setting organization public mode - */ -export const setPublicModeMutation = HazelRpcClient.mutation("organization.setPublicMode") - -/** - * Query atom factory for getting public organization info by slug - */ -export const getOrgBySlugPublicQuery = (slug: string) => - HazelRpcClient.query("organization.getBySlugPublic", { slug }) - -/** - * Mutation atom for joining an organization via public invite - */ -export const joinViaPublicInviteMutation = HazelRpcClient.mutation("organization.joinViaPublicInvite") diff --git a/apps/web/src/atoms/panel-atoms.ts b/apps/web/src/atoms/panel-atoms.ts deleted file mode 100644 index 8908f4b5e..000000000 --- a/apps/web/src/atoms/panel-atoms.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Atom } from "effect/reactivity" -import { useAtomSet, useAtomValue } from "@effect/atom-react" -import { Schema } from "effect" -import { useCallback } from "react" -import { platformStorageRuntime } from "~/lib/platform-storage" - -/** - * Panel types for the split panel system - */ -export type PanelType = "thread" | "profile" | "search" - -/** - * Default widths for each panel type - */ -export const DEFAULT_PANEL_WIDTHS: Record = { - thread: 480, - profile: 400, - search: 520, -} - -/** - * Panel width constraints - */ -export const PANEL_CONSTRAINTS = { - minWidth: 320, - maxWidth: 800, - snapPoints: [0.33, 0.5, 0.67] as const, - snapThreshold: 20, -} as const - -/** - * Atom family that stores panel widths per type - * Persisted to localStorage for user preference retention - */ -export const panelWidthAtomFamily = Atom.family((panelType: PanelType) => - Atom.kvs({ - runtime: platformStorageRuntime, - key: `panel_width_${panelType}`, - schema: Schema.toCodecIso(Schema.NullOr(Schema.Number)), - defaultValue: () => DEFAULT_PANEL_WIDTHS[panelType], - }), -) - -/** - * React hook for panel width state and actions - * Use this in React components to properly trigger re-renders - * - * @example - * const { width, setWidth } = usePanelWidth("thread") - * - */ -export const usePanelWidth = (panelType: PanelType) => { - const width = useAtomValue(panelWidthAtomFamily(panelType)) - const setWidthAtom = useAtomSet(panelWidthAtomFamily(panelType)) - - const setWidth = useCallback( - (newWidth: number) => { - const clamped = Math.max( - PANEL_CONSTRAINTS.minWidth, - Math.min(PANEL_CONSTRAINTS.maxWidth, newWidth), - ) - setWidthAtom(clamped) - }, - [setWidthAtom], - ) - - return { - width: width ?? DEFAULT_PANEL_WIDTHS[panelType], - setWidth, - } -} - -/** - * Snap to nearest snap point if within threshold - */ -export const snapToNearestPoint = ( - width: number, - containerWidth: number, - snapPoints: readonly number[] = PANEL_CONSTRAINTS.snapPoints, - threshold: number = PANEL_CONSTRAINTS.snapThreshold, -): number => { - for (const fraction of snapPoints) { - const snapWidth = containerWidth * fraction - if (Math.abs(width - snapWidth) <= threshold) { - return snapWidth - } - } - return width -} - -/** - * Clamp width to constraints, optionally using container width for max - */ -export const clampPanelWidth = ( - width: number, - minWidth: number = PANEL_CONSTRAINTS.minWidth, - maxWidth: number = PANEL_CONSTRAINTS.maxWidth, - containerWidth?: number, -): number => { - const effectiveMax = containerWidth ? Math.min(maxWidth, containerWidth * 0.7) : maxWidth - return Math.max(minWidth, Math.min(effectiveMax, width)) -} diff --git a/apps/web/src/atoms/presence-atoms.ts b/apps/web/src/atoms/presence-atoms.ts deleted file mode 100644 index a8d82afe8..000000000 --- a/apps/web/src/atoms/presence-atoms.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Atom } from "effect/reactivity" - -/** - * Shared "now" signal used to periodically re-render presence UI. - * Lets us derive effective Offline status from `lastSeenAt` even if the DB row doesn't change. - */ -export const presenceNowSignal = Atom.readable((get) => { - const now = Date.now() - - const intervalId = setInterval(() => { - get.setSelf(Date.now()) - }, 5_000) - - get.addFinalizer(() => { - clearInterval(intervalId) - }) - - return now -}) diff --git a/apps/web/src/atoms/react-scan-atoms.ts b/apps/web/src/atoms/react-scan-atoms.ts deleted file mode 100644 index c0fa0e5d0..000000000 --- a/apps/web/src/atoms/react-scan-atoms.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Atom } from "effect/reactivity" -import { Schema } from "effect" -import { platformStorageRuntime } from "~/lib/platform-storage" - -export const reactScanEnabledAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "react-scan-enabled", - schema: Schema.toCodecIso(Schema.NullOr(Schema.Boolean)), - defaultValue: () => false, -}) diff --git a/apps/web/src/atoms/recent-channels-atom.ts b/apps/web/src/atoms/recent-channels-atom.ts deleted file mode 100644 index a2f48f70d..000000000 --- a/apps/web/src/atoms/recent-channels-atom.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Atom } from "effect/reactivity" -import { Schema } from "effect" -import { platformStorageRuntime } from "~/lib/platform-storage" - -export const MAX_RECENT_CHANNELS = 8 - -/** - * Schema for a single recent channel entry - */ -const RecentChannelSchema = Schema.Struct({ - channelId: Schema.String, - visitedAt: Schema.Number, -}) - -export type RecentChannel = typeof RecentChannelSchema.Type - -/** - * Schema for the array of recent channels - */ -const RecentChannelsSchema = Schema.Array(RecentChannelSchema) - -/** - * Atom that stores recent channels in localStorage - * Automatically persists changes - no manual localStorage calls needed - */ -export const recentChannelsAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "recentChannels", - schema: Schema.toCodecIso(RecentChannelsSchema), - defaultValue: () => [] as RecentChannel[], -}).pipe(Atom.keepAlive) diff --git a/apps/web/src/atoms/rss-subscription-atoms.ts b/apps/web/src/atoms/rss-subscription-atoms.ts deleted file mode 100644 index c47877a5b..000000000 --- a/apps/web/src/atoms/rss-subscription-atoms.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { RssSubscription } from "@hazel/domain/models" -import type { Schema } from "effect" -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Type for RSS subscription data returned from RPC. - * Inferred from the domain model's JSON schema to stay in sync automatically. - */ -export type RssSubscriptionData = Schema.Schema.Type - -/** - * Mutation atom for creating an RSS subscription. - * Subscribes a channel to an RSS feed URL. - */ -export const createRssSubscriptionMutation = HazelRpcClient.mutation("rssSubscription.create") - -/** - * Mutation atom for listing RSS subscriptions for a channel. - */ -export const listRssSubscriptionsMutation = HazelRpcClient.mutation("rssSubscription.list") - -/** - * Mutation atom for listing all RSS subscriptions for an organization. - * Used by the organization-level integration settings page. - */ -export const listOrganizationRssSubscriptionsMutation = HazelRpcClient.mutation( - "rssSubscription.listByOrganization", -) - -/** - * Mutation atom for updating an RSS subscription. - * Can update isEnabled and pollingIntervalMinutes. - */ -export const updateRssSubscriptionMutation = HazelRpcClient.mutation("rssSubscription.update") - -/** - * Mutation atom for deleting an RSS subscription (soft delete). - */ -export const deleteRssSubscriptionMutation = HazelRpcClient.mutation("rssSubscription.delete") diff --git a/apps/web/src/atoms/search-atoms.ts b/apps/web/src/atoms/search-atoms.ts deleted file mode 100644 index 1f920b5e9..000000000 --- a/apps/web/src/atoms/search-atoms.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Atom } from "effect/reactivity" -import { Schema } from "effect" -import { platformStorageRuntime } from "~/lib/platform-storage" - -export const MAX_RECENT_SEARCHES = 10 - -/** - * Schema for a resolved search filter - */ -const SearchFilterSchema = Schema.Struct({ - type: Schema.Literals(["from", "in", "has", "before", "after"]), - value: Schema.String, - displayValue: Schema.String, - id: Schema.String, -}) - -/** - * Schema for a saved recent search - */ -const RecentSearchSchema = Schema.Struct({ - query: Schema.String, - filters: Schema.Array(SearchFilterSchema), - timestamp: Schema.Number, -}) - -export type RecentSearch = typeof RecentSearchSchema.Type - -/** - * Schema for the array of recent searches - */ -const RecentSearchesSchema = Schema.Array(RecentSearchSchema) - -/** - * Atom that stores recent searches in localStorage - * This is persisted separately from the command palette state - * because recent searches should persist across sessions - */ -export const recentSearchesAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "recentSearches", - schema: Schema.toCodecIso(RecentSearchesSchema), - defaultValue: () => [] as RecentSearch[], -}).pipe(Atom.keepAlive) diff --git a/apps/web/src/atoms/section-collapse-atoms.ts b/apps/web/src/atoms/section-collapse-atoms.ts deleted file mode 100644 index 86be098ae..000000000 --- a/apps/web/src/atoms/section-collapse-atoms.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Atom } from "effect/reactivity" -import type { ChannelSectionId } from "@hazel/schema" -import { Schema } from "effect" -import { platformStorageRuntime } from "~/lib/platform-storage" - -/** - * Schema for collapsed sections state - * Maps section IDs to their collapsed state - */ -const CollapsedSectionsSchema = Schema.Record(Schema.String, Schema.Boolean) - -/** - * Atom that stores collapsed state for all sections - * Persisted to localStorage as "section_collapsed_state" - */ -export const collapsedSectionsAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "section_collapsed_state", - schema: Schema.toCodecIso(CollapsedSectionsSchema), - defaultValue: () => ({}) as Record, -}) - -/** - * Atom family that returns the collapsed state for a specific section - */ -export const sectionCollapsedAtomFamily = Atom.family((sectionId: ChannelSectionId | "default" | "dms") => - Atom.make((get) => { - const collapsedSections = get(collapsedSectionsAtom) - return collapsedSections[sectionId] ?? false - }), -) diff --git a/apps/web/src/atoms/sidebar-atoms.ts b/apps/web/src/atoms/sidebar-atoms.ts deleted file mode 100644 index 31a594af1..000000000 --- a/apps/web/src/atoms/sidebar-atoms.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Atom } from "effect/reactivity" -import { Schema } from "effect" -import { platformStorageRuntime } from "~/lib/platform-storage" - -/** - * Sidebar state type - */ -export type SidebarState = "expanded" | "collapsed" - -/** - * Atom that stores whether the sidebar is open (desktop) - * Persisted to localStorage as "sidebar_state" for backwards compatibility - */ -export const sidebarOpenAtom = Atom.kvs({ - runtime: platformStorageRuntime, - key: "sidebar_state", - schema: Schema.toCodecIso(Schema.NullOr(Schema.Boolean)), - defaultValue: () => true, -}) - -/** - * Atom that stores whether the mobile sidebar is open - * Not persisted, resets on page load - */ -export const sidebarOpenMobileAtom = Atom.make(false).pipe(Atom.keepAlive) - -/** - * Derived atom that returns the sidebar state ("expanded" or "collapsed") - */ -export const sidebarStateAtom = Atom.make((get) => { - const isOpen = get(sidebarOpenAtom) - return isOpen ? ("expanded" as const) : ("collapsed" as const) -}).pipe(Atom.keepAlive) - -/** - * Helper function to toggle the desktop sidebar - */ -export const toggleSidebar = () => { - Atom.batch(() => { - return Atom.update(sidebarOpenAtom, (open) => !open) - }) -} - -/** - * Helper function to toggle the mobile sidebar - */ -export const toggleMobileSidebar = () => { - Atom.batch(() => { - return Atom.update(sidebarOpenMobileAtom, (open) => !open) - }) -} - -/** - * Helper function to set the sidebar state - */ -export const setSidebarOpen = (open: boolean) => { - Atom.batch(() => { - return Atom.set(sidebarOpenAtom, open) - }) -} - -/** - * Helper function to set the mobile sidebar state - */ -export const setMobileSidebarOpen = (open: boolean) => { - Atom.batch(() => { - return Atom.set(sidebarOpenMobileAtom, open) - }) -} diff --git a/apps/web/src/atoms/tauri-update-atoms.test.ts b/apps/web/src/atoms/tauri-update-atoms.test.ts deleted file mode 100644 index 605f52d80..000000000 --- a/apps/web/src/atoms/tauri-update-atoms.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Effect } from "effect" -import { beforeEach, describe, expect, it, vi } from "vitest" - -const getTauriUpdaterMock = vi.fn() -const getTauriProcessMock = vi.fn() - -vi.mock("@hazel/desktop/bridge", () => ({ - getTauriUpdater: getTauriUpdaterMock, - getTauriProcess: getTauriProcessMock, -})) - -describe("tauri-update-atoms", () => { - beforeEach(() => { - vi.clearAllMocks() - vi.resetModules() - }) - - it("checks for updates even when process plugin is unavailable", async () => { - getTauriUpdaterMock.mockReturnValue({ - check: vi.fn().mockResolvedValue(null), - }) - getTauriProcessMock.mockReturnValue(undefined) - - const { checkForUpdates } = await import("./tauri-update-atoms") - const states: Array<{ _tag: string }> = [] - - await checkForUpdates((state) => { - states.push(state as { _tag: string }) - }) - - expect(states.map((state) => state._tag)).toEqual(["checking", "not-available"]) - }) - - it("falls back to app exit when relaunch fails after install", async () => { - const relaunch = vi.fn().mockRejectedValue(new Error("relaunch failed")) - const exit = vi.fn().mockResolvedValue(undefined) - getTauriUpdaterMock.mockReturnValue({ - check: vi.fn(), - }) - getTauriProcessMock.mockReturnValue({ relaunch, exit }) - - const { createDownloadEffect } = await import("./tauri-update-atoms") - - const update = { - download: vi.fn(async (onEvent: (event: unknown) => void) => { - onEvent({ event: "Started", data: { contentLength: 10 } }) - onEvent({ event: "Progress", data: { chunkLength: 10 } }) - onEvent({ event: "Finished", data: {} }) - }), - install: vi.fn().mockResolvedValue(undefined), - } - - const states: Array<{ _tag: string; message?: string }> = [] - await Effect.runPromise( - createDownloadEffect(update as any, (state) => { - states.push(state as { _tag: string; message?: string }) - }), - ) - - expect(relaunch).toHaveBeenCalledTimes(1) - expect(exit).toHaveBeenCalledTimes(1) - expect(states.some((state) => state._tag === "error")).toBe(false) - }) - - it("surfaces a manual restart message when both relaunch and exit fail", async () => { - const relaunch = vi.fn().mockRejectedValue(new Error("relaunch failed")) - const exit = vi.fn().mockRejectedValue(new Error("exit failed")) - getTauriUpdaterMock.mockReturnValue({ - check: vi.fn(), - }) - getTauriProcessMock.mockReturnValue({ relaunch, exit }) - - const { createDownloadEffect } = await import("./tauri-update-atoms") - - const update = { - download: vi.fn(async (_onEvent: (event: unknown) => void) => {}), - install: vi.fn().mockResolvedValue(undefined), - } - - const states: Array<{ _tag: string; message?: string }> = [] - await Effect.runPromise( - createDownloadEffect(update as any, (state) => { - states.push(state as { _tag: string; message?: string }) - }), - ) - - expect(relaunch).toHaveBeenCalledTimes(1) - expect(exit).toHaveBeenCalledTimes(1) - expect(states.at(-1)).toEqual({ - _tag: "error", - message: - "Update installed, but restart failed and Hazel could not quit automatically. Please quit and reopen Hazel manually.", - }) - }) -}) diff --git a/apps/web/src/atoms/tauri-update-atoms.ts b/apps/web/src/atoms/tauri-update-atoms.ts deleted file mode 100644 index 300f5c3b9..000000000 --- a/apps/web/src/atoms/tauri-update-atoms.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @module Tauri update atoms - * @platform desktop - * @description Effect Atom-based state management for Tauri app updates - */ - -import { Atom } from "effect/reactivity" -import { - getTauriProcess, - getTauriUpdater, - type TauriProcessApi, - type TauriUpdaterApi, -} from "@hazel/desktop/bridge" -import { Data, Duration, Effect } from "effect" - -type TauriUpdate = Awaited> -type DownloadCallback = NonNullable["download"]>[0]> -type DownloadEvent = Parameters[0] - -/** - * Tagged error classes for update operations - */ -export class UpdateCheckError extends Data.TaggedError("UpdateCheckError")<{ - message: string -}> {} - -export class UpdateDownloadError extends Data.TaggedError("UpdateDownloadError")<{ - message: string -}> {} - -export class UpdateInstallError extends Data.TaggedError("UpdateInstallError")<{ - message: string -}> {} - -export class UpdateRelaunchError extends Data.TaggedError("UpdateRelaunchError")<{ - message: string -}> {} - -export class UpdateExitError extends Data.TaggedError("UpdateExitError")<{ - message: string -}> {} - -const updater: TauriUpdaterApi | undefined = getTauriUpdater() -const process: TauriProcessApi | undefined = getTauriProcess() - -/** - * Update check state - */ -export type TauriUpdateState = - | { _tag: "idle" } - | { _tag: "checking" } - | { _tag: "available"; version: string; body: string | null; update: NonNullable } - | { _tag: "not-available"; lastCheckedAt: Date } - | { _tag: "error"; message: string } - -/** - * Download/install progress state - */ -export type TauriDownloadState = - | { _tag: "idle" } - | { _tag: "downloading"; downloadedBytes: number; totalBytes: number | undefined } - | { _tag: "installing" } - | { _tag: "restarting" } - | { _tag: "error"; message: string } - -/** - * Writable atom holding the update check result - */ -export const tauriUpdateStateAtom = Atom.make({ _tag: "idle" }).pipe(Atom.keepAlive) - -/** - * Writable atom holding the download/install progress - */ -export const tauriDownloadStateAtom = Atom.make({ _tag: "idle" }).pipe(Atom.keepAlive) - -/** - * Check for updates interval (6 hours) in milliseconds - */ -export const UPDATE_CHECK_INTERVAL_MS = Duration.toMillis(Duration.hours(6)) - -/** - * Checks for updates and calls the setter with the result. - * This is a plain async function that works with React's useAtomSet. - */ -export async function checkForUpdates(setUpdateState: (state: TauriUpdateState) => void): Promise { - if (!updater) return - - setUpdateState({ _tag: "checking" }) - - try { - const update = await updater.check() - - if (update) { - setUpdateState({ - _tag: "available", - version: update.version, - body: update.body ?? null, - update, - }) - } else { - setUpdateState({ _tag: "not-available", lastCheckedAt: new Date() }) - } - } catch (error) { - console.error("[update] Check failed:", error) - setUpdateState({ - _tag: "error", - message: error instanceof Error ? error.message : "Update check failed", - }) - } -} - -/** - * Creates an Effect that downloads and installs an update. - * The component should pass the setDownloadState function to update progress. - * - * Uses separate download() and install() phases for better progress tracking - * and error handling. - */ -export const createDownloadEffect = ( - update: NonNullable, - setDownloadState: (state: TauriDownloadState) => void, -) => - Effect.gen(function* () { - let downloadedBytes = 0 - let totalBytes: number | undefined - - // Phase 1: Download with progress tracking - yield* Effect.tryPromise({ - try: () => - update.download((event: DownloadEvent) => { - console.log("[update] Download event:", event.event) - switch (event.event) { - case "Started": - console.log("[update] Content length:", event.data.contentLength) - totalBytes = event.data.contentLength ?? undefined - setDownloadState({ - _tag: "downloading", - downloadedBytes: 0, - totalBytes, - }) - break - case "Progress": - downloadedBytes += event.data.chunkLength - setDownloadState({ - _tag: "downloading", - downloadedBytes, - totalBytes, - }) - break - case "Finished": - console.log("[update] Download finished") - break - } - }), - catch: (error) => - new UpdateDownloadError({ - message: error instanceof Error ? error.message : "Download failed", - }), - }) - - // Phase 2: Install - setDownloadState({ _tag: "installing" }) - yield* Effect.tryPromise({ - try: () => update.install(), - catch: (error) => - new UpdateInstallError({ - message: error instanceof Error ? error.message : "Installation failed", - }), - }) - - // Phase 3: Restart with delay to ensure installation completes - setDownloadState({ _tag: "restarting" }) - yield* Effect.sleep(Duration.millis(500)) - - if (!process) { - return yield* Effect.fail( - new UpdateRelaunchError({ - message: - "Update installed, but restart is unavailable. Please quit and reopen Hazel manually.", - }), - ) - } - - yield* Effect.tryPromise({ - try: () => process.relaunch(), - catch: (error) => - new UpdateRelaunchError({ - message: - error instanceof Error - ? error.message - : "Automatic restart failed after installing the update.", - }), - }) - }).pipe( - Effect.catchTags({ - UpdateDownloadError: (error) => - Effect.sync(() => { - console.error("[update] Download failed:", error.message) - setDownloadState({ _tag: "error", message: error.message }) - }), - UpdateInstallError: (error) => - Effect.sync(() => { - console.error("[update] Install failed:", error.message) - setDownloadState({ _tag: "error", message: error.message }) - }), - UpdateRelaunchError: (error) => - Effect.gen(function* () { - console.error("[update] Relaunch failed:", error.message) - - if (!process) { - setDownloadState({ - _tag: "error", - message: - "Update installed, but restart is unavailable. Please quit and reopen Hazel manually.", - }) - return - } - - yield* Effect.tryPromise({ - try: () => process.exit(0), - catch: () => - new UpdateExitError({ - message: - "Update installed, but restart failed and Hazel could not quit automatically. Please quit and reopen Hazel manually.", - }), - }).pipe( - Effect.catchTag("UpdateExitError", (exitError) => - Effect.sync(() => { - console.error("[update] Exit fallback failed:", exitError.message) - setDownloadState({ _tag: "error", message: exitError.message }) - }), - ), - ) - }), - }), - ) - -/** - * Check if we're in a Tauri environment - */ -export const isTauriEnvironment = !!updater diff --git a/apps/web/src/atoms/typing-indicator-atom.ts b/apps/web/src/atoms/typing-indicator-atom.ts deleted file mode 100644 index d56d60be2..000000000 --- a/apps/web/src/atoms/typing-indicator-atom.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Mutation atom for creating/updating typing indicators - * Use with useAtomSet in React components - */ -export const upsertTypingIndicatorMutation = HazelRpcClient.mutation("typingIndicator.create") - -/** - * Mutation atom for deleting typing indicators - * Use with useAtomSet in React components - */ -export const deleteTypingIndicatorMutation = HazelRpcClient.mutation("typingIndicator.delete") diff --git a/apps/web/src/atoms/user-atoms.ts b/apps/web/src/atoms/user-atoms.ts deleted file mode 100644 index ed2c5bc4d..000000000 --- a/apps/web/src/atoms/user-atoms.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client" - -/** - * Mutation atom for updating Hazel-specific user preferences (timezone, settings). - * Identity fields (firstName/lastName/avatarUrl) are owned by Clerk — use - * `useUser().user.update(...)` / `setProfileImage(...)` on the client instead. - */ -export const updateUserMutation = HazelRpcClient.mutation("user.update") - -/** - * Mutation atom for finalizing user onboarding - */ -export const finalizeOnboardingMutation = HazelRpcClient.mutation("user.finalizeOnboarding") diff --git a/apps/web/src/components/app-nav.tsx b/apps/web/src/components/app-nav.tsx deleted file mode 100644 index e19e3b751..000000000 --- a/apps/web/src/components/app-nav.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client" - -import { IconBellSlash } from "~/components/icons/icon-bell-slash" -import { IconChatBubble } from "~/components/icons/icon-chat-bubble" -import IconUsers from "~/components/icons/icon-users" -import IconMagnifier from "~/components/icons/icon-magnifier-3" -import { Button as PrimitiveButton } from "~/components/ui/button" -import { Separator } from "~/components/ui/separator" -import { SidebarTrigger } from "~/components/ui/sidebar" -import { isTauriMacOS } from "~/lib/tauri" - -export function AppNav({ openCmd }: { openCmd: (open: boolean) => void }) { - const hasTauriTitlebar = isTauriMacOS() - - return ( - - ) -} - -export function Button(props: React.ComponentProps) { - return -} diff --git a/apps/web/src/components/bots/bot-avatar-upload.tsx b/apps/web/src/components/bots/bot-avatar-upload.tsx deleted file mode 100644 index f4c686b73..000000000 --- a/apps/web/src/components/bots/bot-avatar-upload.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { useState } from "react" -import { Button, type DropItem, DropZone, FileTrigger } from "react-aria-components" -import { toast } from "sonner" -import { BotAvatar } from "~/components/bots/bot-avatar" -import IconEdit from "~/components/icons/icon-edit" -import { AvatarCropModal } from "~/components/profile/avatar-crop-modal" -import { Loader } from "~/components/ui/loader" -import type { BotWithUser } from "~/db/hooks" -import { useBotAvatarUpload } from "~/hooks/use-bot-avatar-upload" -import { useDragDetection } from "~/hooks/use-drag-detection" -import { cx } from "~/utils/cx" - -const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB -const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"] - -interface BotAvatarUploadProps { - bot: BotWithUser - className?: string -} - -export function BotAvatarUpload({ bot, className }: BotAvatarUploadProps) { - const { uploadBotAvatar, isUploading, uploadProgress } = useBotAvatarUpload(bot.id) - const { isDraggingOnPage } = useDragDetection() - const [selectedFile, setSelectedFile] = useState(null) - const [isCropModalOpen, setIsCropModalOpen] = useState(false) - - // Shared file validation and processing - const processFile = (file: File) => { - if (!ALLOWED_TYPES.includes(file.type)) { - toast.error("Invalid file type", { - description: "Please select a JPEG, PNG, or WebP image", - }) - return - } - - if (file.size > MAX_FILE_SIZE) { - toast.error("File too large", { - description: "Image must be less than 5MB", - }) - return - } - - setSelectedFile(file) - setIsCropModalOpen(true) - } - - // Handle files from FileTrigger (click to upload) - const handleFileSelect = (files: FileList | null) => { - const file = files?.[0] - if (file) processFile(file) - } - - // Handle files from DropZone (drag and drop) - const handleDrop = async (e: { items: DropItem[] }) => { - const fileItem = e.items.find( - (item): item is DropItem & { kind: "file"; getFile: () => Promise } => item.kind === "file", - ) - if (!fileItem) return - - const file = await fileItem.getFile() - processFile(file) - } - - const handleCropComplete = async (croppedBlob: Blob) => { - setIsCropModalOpen(false) - - // Convert Blob to File for existing upload function - const croppedFile = new File([croppedBlob], "avatar.webp", { type: "image/webp" }) - await uploadBotAvatar(croppedFile) - - setSelectedFile(null) - } - - const handleCropModalOpenChange = (open: boolean) => { - setIsCropModalOpen(open) - if (!open) { - setSelectedFile(null) - } - } - - return ( -
- - types.has("image/jpeg") || types.has("image/png") || types.has("image/webp") - ? "copy" - : "cancel" - } - onDrop={handleDrop} - isDisabled={isUploading} - className="rounded-xl focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2" - > - {({ isDropTarget }) => ( - - - - )} - - - {/* Helper text */} -

Click or drop image

- - {/* Avatar cropping modal */} - -
- ) -} diff --git a/apps/web/src/components/bots/bot-avatar.tsx b/apps/web/src/components/bots/bot-avatar.tsx deleted file mode 100644 index 07b49eee2..000000000 --- a/apps/web/src/components/bots/bot-avatar.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useAtomValue } from "@effect/atom-react" -import type { IntegrationConnection } from "@hazel/domain/models" -import { Avatar, type AvatarProps } from "~/components/ui/avatar" -import { resolveBotAvatarUrl } from "~/lib/bot-avatar" -import { resolvedThemeAtom } from "~/components/theme-provider" - -type IntegrationProvider = IntegrationConnection.IntegrationProvider - -interface BotAvatarProps extends Omit { - bot: { - id?: string - user?: { avatarUrl?: string | null } | null - allowedIntegrations?: readonly IntegrationProvider[] | null - name?: string - } -} - -/** - * Avatar component for bots with smart avatar resolution. - * - * Resolution order: - * 1. Custom avatarUrl if set - * 2. Brandfetch icon if bot has exactly one allowedIntegration - * 3. Robot icon fallback - */ -export function BotAvatar({ bot, className, ...props }: BotAvatarProps) { - const theme = useAtomValue(resolvedThemeAtom) - const avatarUrl = resolveBotAvatarUrl(bot, theme) - - return ( - - ) -} diff --git a/apps/web/src/components/bots/bot-card.tsx b/apps/web/src/components/bots/bot-card.tsx deleted file mode 100644 index 84c9259ae..000000000 --- a/apps/web/src/components/bots/bot-card.tsx +++ /dev/null @@ -1,304 +0,0 @@ -import { useAtomSet } from "@effect/atom-react" -import type { IntegrationConnection } from "@hazel/domain/models" -import { useState } from "react" - -type IntegrationProvider = IntegrationConnection.IntegrationProvider -import { deleteBotMutation, regenerateBotTokenMutation } from "~/atoms/bot-atoms" -import { BotAvatar } from "~/components/bots/bot-avatar" -import { BotTokenDisplay } from "~/components/bots/bot-token-display" -import IconArrowPath from "~/components/icons/icon-arrow-path" -import IconDotsVertical from "~/components/icons/icon-dots-vertical" -import IconEdit from "~/components/icons/icon-edit" -import IconTrash from "~/components/icons/icon-trash" -import { EditBotModal } from "~/components/modals/edit-bot-modal" -import type { BotWithUser } from "~/db/hooks" -import { Badge } from "~/components/ui/badge" -import { Button } from "~/components/ui/button" -import { Menu, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger } from "~/components/ui/menu" -import { - Modal, - ModalBody, - ModalContent, - ModalDescription, - ModalFooter, - ModalHeader, - ModalTitle, -} from "~/components/ui/modal" -import { getIntegrationIconUrl, groupScopesByResource, INTEGRATION_PROVIDERS } from "~/lib/bot-scopes" -import { exitToastAsync } from "~/lib/toast-exit" - -// Discriminated union for modal state - eliminates 6 separate useState calls -type ModalState = - | { type: "closed" } - | { type: "edit" } - | { type: "delete-confirm"; isDeleting: boolean } - | { type: "regenerate-confirm"; isRegenerating: boolean; token: string | null } - -interface BotCardProps { - bot: BotWithUser - showUninstall?: boolean - onDelete?: () => void - onUpdate?: () => void - onUninstall?: () => void - reactivityKeys?: readonly string[] -} - -export function BotCard({ - bot, - showUninstall, - onDelete, - onUpdate, - onUninstall, - reactivityKeys, -}: BotCardProps) { - const [modalState, setModalState] = useState({ type: "closed" }) - - const deleteBot = useAtomSet(deleteBotMutation, { mode: "promiseExit" }) - const regenerateToken = useAtomSet(regenerateBotTokenMutation, { mode: "promiseExit" }) - - const closeModal = () => setModalState({ type: "closed" }) - - const handleDelete = async () => { - setModalState({ type: "delete-confirm", isDeleting: true }) - await exitToastAsync(deleteBot({ payload: { id: bot.id }, reactivityKeys })) - .loading("Deleting application...") - .onSuccess(() => { - closeModal() - onDelete?.() - }) - .successMessage("Application deleted successfully") - .onErrorTag("BotNotFoundError", () => ({ - title: "Application not found", - description: "This application may have already been deleted.", - isRetryable: false, - })) - .run() - setModalState((prev) => (prev.type === "delete-confirm" ? { ...prev, isDeleting: false } : prev)) - } - - const handleRegenerateToken = async () => { - setModalState({ type: "regenerate-confirm", isRegenerating: true, token: null }) - await exitToastAsync(regenerateToken({ payload: { id: bot.id } })) - .loading("Regenerating token...") - .onSuccess((result) => - setModalState({ type: "regenerate-confirm", isRegenerating: false, token: result.token }), - ) - .successMessage("Token regenerated successfully") - .onErrorTag("BotNotFoundError", () => ({ - title: "Application not found", - description: "This application may have been deleted.", - isRetryable: false, - })) - .onErrorTag("RateLimitExceededError", () => ({ - title: "Rate limit exceeded", - description: "Please wait before trying again.", - isRetryable: true, - })) - .run() - setModalState((prev) => - prev.type === "regenerate-confirm" ? { ...prev, isRegenerating: false } : prev, - ) - } - - // Derived state from discriminated union - const isDeleteConfirmOpen = modalState.type === "delete-confirm" - const isRegenerateConfirmOpen = modalState.type === "regenerate-confirm" - const isEditModalOpen = modalState.type === "edit" - const isDeleting = modalState.type === "delete-confirm" && modalState.isDeleting - const isRegenerating = modalState.type === "regenerate-confirm" && modalState.isRegenerating - const regeneratedToken = modalState.type === "regenerate-confirm" ? modalState.token : null - - const scopes = bot.scopes ?? [] - const scopeGroups = groupScopesByResource(scopes) - const integrations = ((bot.allowedIntegrations ?? []) as IntegrationProvider[]).filter( - (p) => INTEGRATION_PROVIDERS[p], - ) - - return ( - <> -
- {/* Header */} -
- -
-
-

{bot.name}

- {bot.isPublic && ( - - Public - - )} -
-

- {bot.description || "No description"} -

-
- - {/* Actions */} - {!showUninstall && ( - - - - - - setModalState({ type: "edit" })}> - - Edit - - - setModalState({ - type: "regenerate-confirm", - isRegenerating: false, - token: null, - }) - } - > - - Regenerate Token - - - - setModalState({ type: "delete-confirm", isDeleting: false }) - } - intent="danger" - > - - Delete - - - - )} -
- - {/* Permissions */} -
- {scopes.length > 0 && ( -
- {Object.entries(scopeGroups).map(([resource, actions]) => ( - - {resource} - · - {actions.join(", ")} - - ))} - {integrations.length > 0 && - integrations.map((provider) => ( - - {INTEGRATION_PROVIDERS[provider].label} - - ))} -
- )} -
- - {/* Footer - only show when there's an action */} - {showUninstall && ( -
- -
- )} -
- - {/* Edit Modal */} - (open ? setModalState({ type: "edit" }) : closeModal())} - bot={bot} - onSuccess={onUpdate} - reactivityKeys={reactivityKeys} - /> - - {/* Delete Confirmation Modal */} - - open ? setModalState({ type: "delete-confirm", isDeleting: false }) : closeModal() - } - > - - - Delete Application - - Are you sure you want to delete "{bot.name}"? This action cannot be undone. - - - - - - - - - - {/* Regenerate Token Confirmation Modal */} - !open && closeModal()}> - - - - {regeneratedToken ? "New Token Generated" : "Regenerate Token"} - - - {regeneratedToken - ? "Save this new token now. The old token has been invalidated." - : "This will invalidate the current token. The application will need to be updated with the new token."} - - - - {regeneratedToken ? ( - - ) : ( -

- Any applications using the current token will stop working immediately. -

- )} -
- - {regeneratedToken ? ( - - ) : ( - <> - - - - )} - -
-
- - ) -} diff --git a/apps/web/src/components/bots/bot-token-display.tsx b/apps/web/src/components/bots/bot-token-display.tsx deleted file mode 100644 index d7c7fedf1..000000000 --- a/apps/web/src/components/bots/bot-token-display.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from "react" -import IconCopy from "~/components/icons/icon-copy" -import IconEye from "~/components/icons/icon-eye" -import IconEyeSlash from "~/components/icons/icon-eye-slash" -import IconWarning from "~/components/icons/icon-warning" -import { Button } from "~/components/ui/button" -import { toast } from "sonner" - -interface BotTokenDisplayProps { - token: string -} - -export function BotTokenDisplay({ token }: BotTokenDisplayProps) { - const [isVisible, setIsVisible] = useState(false) - - const handleCopy = async () => { - try { - await navigator.clipboard.writeText(token) - toast.success("Token copied to clipboard") - } catch { - toast.error("Failed to copy token") - } - } - - return ( -
- {/* Warning */} -
- -

- Save this token now.{" "} - - You won't be able to see it again after closing this dialog. - -

-
- - {/* Token display */} -
- - {isVisible ? token : "•".repeat(Math.min(40, token.length))} - - - -
- - {/* Usage hint */} -

- Use this token with the{" "} - @hazel/bot-sdk to - authenticate your application. -

-
- ) -} diff --git a/apps/web/src/components/bots/marketplace-bot-card.tsx b/apps/web/src/components/bots/marketplace-bot-card.tsx deleted file mode 100644 index 09e28ab91..000000000 --- a/apps/web/src/components/bots/marketplace-bot-card.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import type { IntegrationConnection } from "@hazel/domain/models" -import { useState } from "react" -import { BotAvatar } from "~/components/bots/bot-avatar" -import type { BotWithUser } from "~/db/hooks" -import IconCheck from "~/components/icons/icon-check" -import IconDownload from "~/components/icons/icon-download" -import { Button } from "~/components/ui/button" -import { getIntegrationIconUrl, groupScopesByResource, INTEGRATION_PROVIDERS } from "~/lib/bot-scopes" - -type IntegrationProvider = IntegrationConnection.IntegrationProvider - -export interface PublicBotWithUser extends BotWithUser { - isInstalled: boolean - creatorName: string -} - -interface MarketplaceBotCardProps { - bot: PublicBotWithUser - onInstall: () => void -} - -export function MarketplaceBotCard({ bot, onInstall }: MarketplaceBotCardProps) { - const [isInstalling, setIsInstalling] = useState(false) - - const handleInstall = async () => { - setIsInstalling(true) - await onInstall() - setIsInstalling(false) - } - - const scopes = bot.scopes ?? [] - const scopeGroups = groupScopesByResource(scopes) - const integrations = ((bot.allowedIntegrations ?? []) as IntegrationProvider[]).filter( - (p) => INTEGRATION_PROVIDERS[p], - ) - - return ( -
- {/* Header */} -
- -
-

{bot.name}

-

by {bot.creatorName}

-
-
- - {/* Description - fixed min height */} -
-

- {bot.description || "No description provided"} -

-
- - {/* Permissions */} -
- {scopes.length > 0 && ( -
- {Object.entries(scopeGroups).map(([resource, actions]) => ( - - {resource} - · - {actions.join(", ")} - - ))} - {integrations.length > 0 && - integrations.map((provider) => ( - - {INTEGRATION_PROVIDERS[provider].label} - - ))} -
- )} -
- - {/* Footer - always at bottom */} -
- - - {bot.installCount.toLocaleString()} - - {bot.isInstalled ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/apps/web/src/components/channel-icon.tsx b/apps/web/src/components/channel-icon.tsx deleted file mode 100644 index 61eb87726..000000000 --- a/apps/web/src/components/channel-icon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import IconHashtag from "~/components/icons/icon-hashtag" - -interface ChannelIconProps { - icon?: string | null - className?: string -} - -export function ChannelIcon({ icon, className }: ChannelIconProps) { - if (icon) { - return ( - - {icon} - - ) - } - - return -} diff --git a/apps/web/src/components/channel-settings/add-github-repo-modal.tsx b/apps/web/src/components/channel-settings/add-github-repo-modal.tsx deleted file mode 100644 index 7da92644a..000000000 --- a/apps/web/src/components/channel-settings/add-github-repo-modal.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import { AsyncResult } from "effect/reactivity" -import { useAtomSet, useAtomValue } from "@effect/atom-react" -import type { OrganizationId } from "@hazel/schema" -import type { GitHubSubscription } from "@hazel/domain/models" -import type { ChannelId } from "@hazel/schema" -import { useState } from "react" -import { createGitHubSubscriptionMutation } from "~/atoms/github-subscription-atoms" -import { Button } from "~/components/ui/button" -import { Checkbox } from "~/components/ui/checkbox" -import { Input, InputGroup } from "~/components/ui/input" -import { - Modal, - ModalBody, - ModalClose, - ModalContent, - ModalFooter, - ModalHeader, - ModalTitle, -} from "~/components/ui/modal" -import { HazelApiClient } from "~/lib/services/common/atom-client" -import { exitToast } from "~/lib/toast-exit" - -type GitHubEventType = typeof GitHubSubscription.GitHubEventType.Type - -const EVENT_OPTIONS: { id: GitHubEventType; label: string; description: string }[] = [ - { id: "push", label: "Push", description: "Commits pushed to branches" }, - { id: "pull_request", label: "Pull Requests", description: "PR opened, closed, or merged" }, - { id: "issues", label: "Issues", description: "Issue opened, closed, or commented" }, - { id: "release", label: "Releases", description: "New releases published" }, - { id: "deployment_status", label: "Deployments", description: "Deployment status changes" }, - { id: "workflow_run", label: "Workflows", description: "GitHub Actions workflow runs" }, - { id: "star", label: "Stars", description: "Repository starred or unstarred" }, -] - -interface AddGitHubRepoModalProps { - channelId: ChannelId - organizationId: OrganizationId - isOpen: boolean - onClose: () => void - onSuccess: () => void -} - -export function AddGitHubRepoModal({ - channelId, - organizationId, - isOpen, - onClose, - onSuccess, -}: AddGitHubRepoModalProps) { - const [selectedRepo, setSelectedRepo] = useState<{ - id: number - fullName: string - owner: string - name: string - } | null>(null) - const [enabledEvents, setEnabledEvents] = useState(["push", "pull_request", "issues"]) - const [branchFilter, setBranchFilter] = useState("") - const [isCreating, setIsCreating] = useState(false) - const [searchQuery, setSearchQuery] = useState("") - - // Fetch repositories - const repositoriesResult = useAtomValue( - HazelApiClient.query("integration-resources", "getGitHubRepositories", { - params: { orgId: organizationId }, - query: { page: 1, perPage: 100 }, - }), - ) - - const createSubscription = useAtomSet(createGitHubSubscriptionMutation, { mode: "promiseExit" }) - - const handleToggleEvent = (eventId: GitHubEventType) => { - setEnabledEvents((prev) => - prev.includes(eventId) ? prev.filter((e) => e !== eventId) : [...prev, eventId], - ) - } - - const handleSubmit = async () => { - if (!selectedRepo || enabledEvents.length === 0) return - - setIsCreating(true) - const exit = await createSubscription({ - payload: { - channelId, - repositoryId: selectedRepo.id, - repositoryFullName: selectedRepo.fullName, - repositoryOwner: selectedRepo.owner, - repositoryName: selectedRepo.name, - enabledEvents: [...enabledEvents], - branchFilter: branchFilter.trim() || null, - }, - }) - - exitToast(exit) - .onSuccess(() => { - onSuccess() - handleClose() - }) - .successMessage(`Subscribed to ${selectedRepo.fullName}`) - .onErrorTag("GitHubSubscriptionExistsError", () => ({ - title: "Already subscribed", - description: "This channel is already subscribed to this repository.", - isRetryable: false, - })) - .onErrorTag("GitHubNotConnectedError", () => ({ - title: "GitHub not connected", - description: "Connect GitHub in organization settings first.", - isRetryable: false, - })) - .onErrorTag("ChannelNotFoundError", () => ({ - title: "Channel not found", - description: "This channel may have been deleted.", - isRetryable: false, - })) - .run() - setIsCreating(false) - } - - const handleClose = () => { - setSelectedRepo(null) - setEnabledEvents(["push", "pull_request", "issues"]) - setBranchFilter("") - setSearchQuery("") - onClose() - } - - // Get repositories from result - const repositories = AsyncResult.builder(repositoriesResult) - .onSuccess((data) => data?.repositories ?? []) - .orElse(() => []) - - // Filter repositories by search query - const filteredRepos = searchQuery - ? repositories.filter((repo) => repo.fullName.toLowerCase().includes(searchQuery.toLowerCase())) - : repositories - - return ( - !open && handleClose()}> - - - Add GitHub Repository - - - -
- {/* Repository selector */} -
- - {AsyncResult.builder(repositoriesResult) - .onInitial(() => ( -
-
-
- Loading repositories... -
-
- )) - .onFailure(() => ( -
-

Failed to load repositories

-
- )) - .onSuccess(() => - selectedRepo ? ( -
-
-
- - - -
- - {selectedRepo.fullName} - -
- -
- ) : ( -
- - setSearchQuery(e.target.value)} - /> - -
- {filteredRepos.length === 0 ? ( -
- {searchQuery - ? "No repositories found" - : "No repositories available"} -
- ) : ( -
- {filteredRepos.map((repo) => ( - - ))} -
- )} -
-
- ), - ) - .render()} -
- - {/* Event types */} - {selectedRepo && ( - <> -
- -

- Select which events to receive notifications for. -

-
- {EVENT_OPTIONS.map((event) => { - const isSelected = enabledEvents.includes(event.id) - return ( - - ) - })} -
-
- - {/* Branch filter (only for push events) */} - {enabledEvents.includes("push") && ( -
- -

- Only receive push events for branches matching this pattern. -

- - setBranchFilter(e.target.value)} - /> - -
- )} - - )} -
- - - - Cancel - - - - - ) -} diff --git a/apps/web/src/components/channel-settings/add-rss-feed-modal.tsx b/apps/web/src/components/channel-settings/add-rss-feed-modal.tsx deleted file mode 100644 index 461f3eabf..000000000 --- a/apps/web/src/components/channel-settings/add-rss-feed-modal.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useAtomSet } from "@effect/atom-react" -import type { ChannelId } from "@hazel/schema" -import { useState } from "react" -import { createRssSubscriptionMutation } from "~/atoms/rss-subscription-atoms" -import { Button } from "~/components/ui/button" -import { Input, InputGroup } from "~/components/ui/input" -import { - Modal, - ModalBody, - ModalClose, - ModalContent, - ModalFooter, - ModalHeader, - ModalTitle, -} from "~/components/ui/modal" -import { exitToast } from "~/lib/toast-exit" - -const POLLING_INTERVAL_OPTIONS = [ - { value: 5, label: "Every 5 minutes" }, - { value: 15, label: "Every 15 minutes" }, - { value: 30, label: "Every 30 minutes" }, - { value: 60, label: "Every hour" }, -] - -interface AddRssFeedModalProps { - channelId: ChannelId - isOpen: boolean - onClose: () => void - onSuccess: () => void -} - -export function AddRssFeedModal({ channelId, isOpen, onClose, onSuccess }: AddRssFeedModalProps) { - const [feedUrl, setFeedUrl] = useState("") - const [pollingInterval, setPollingInterval] = useState(15) - const [isCreating, setIsCreating] = useState(false) - - const createSubscription = useAtomSet(createRssSubscriptionMutation, { mode: "promiseExit" }) - - const isValidUrl = (() => { - try { - const url = new URL(feedUrl.trim()) - const protocol = url.protocol - if (protocol === "https:") return true - if (protocol === "http:") return true - return false - } catch { - return false - } - })() - - const handleSubmit = async () => { - if (!feedUrl.trim() || !isValidUrl) return - - setIsCreating(true) - const exit = await createSubscription({ - payload: { - channelId, - feedUrl: feedUrl.trim(), - pollingIntervalMinutes: pollingInterval, - }, - }) - - exitToast(exit) - .onSuccess(() => { - onSuccess() - handleClose() - }) - .successMessage("RSS feed added") - .onErrorTag("RssSubscriptionExistsError", () => ({ - title: "Already subscribed", - description: "This channel is already subscribed to this feed URL.", - isRetryable: false, - })) - .onErrorTag("RssFeedValidationError", (error) => ({ - title: "Invalid feed URL", - description: error.message, - isRetryable: true, - })) - .onErrorTag("ChannelNotFoundError", () => ({ - title: "Channel not found", - description: "This channel may have been deleted.", - isRetryable: false, - })) - .run() - setIsCreating(false) - } - - const handleClose = () => { - setFeedUrl("") - setPollingInterval(15) - onClose() - } - - return ( - !open && handleClose()}> - - - Add RSS Feed - - - -
-
- -

- Enter the RSS or Atom feed URL. The feed will be validated when you submit. -

- - setFeedUrl(e.target.value)} - /> - - {feedUrl.trim() && !isValidUrl && ( -

- Please enter a valid URL starting with https:// or http:// -

- )} -
- - {isValidUrl && ( -
- -

- How often to check the feed for new items. -

-
- {POLLING_INTERVAL_OPTIONS.map((option) => ( - - ))} -
-
- )} -
-
- - - Cancel - - -
-
- ) -} diff --git a/apps/web/src/components/channel-settings/create-webhook-form.tsx b/apps/web/src/components/channel-settings/create-webhook-form.tsx deleted file mode 100644 index f4242b5c1..000000000 --- a/apps/web/src/components/channel-settings/create-webhook-form.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { useAtomSet } from "@effect/atom-react" -import type { ChannelId } from "@hazel/schema" -import { type } from "arktype" -import { useState } from "react" -import { createChannelWebhookMutation } from "~/atoms/channel-webhook-atoms" -import IconPlus from "~/components/icons/icon-plus" -import { Button } from "~/components/ui/button" -import { Description, FieldError, Label } from "~/components/ui/field" -import { Input } from "~/components/ui/input" -import { TextField } from "~/components/ui/text-field" -import { Textarea } from "~/components/ui/textarea" -import { useAppForm } from "~/hooks/use-app-form" -import { exitToast } from "~/lib/toast-exit" -import { TokenDisplay } from "./token-display" - -const webhookSchema = type({ - name: "1 void -} - -export function CreateWebhookForm({ channelId, onSuccess }: CreateWebhookFormProps) { - const [isExpanded, setIsExpanded] = useState(false) - const [createdWebhook, setCreatedWebhook] = useState<{ - token: string - webhookUrl: string - } | null>(null) - - const createWebhook = useAtomSet(createChannelWebhookMutation, { - mode: "promiseExit", - }) - - const form = useAppForm({ - defaultValues: { - name: "", - description: "", - avatarUrl: "", - } as WebhookFormData, - validators: { - onChange: webhookSchema, - }, - onSubmit: async ({ value }) => { - const exit = await createWebhook({ - payload: { - channelId, - name: value.name, - description: value.description || undefined, - avatarUrl: value.avatarUrl || undefined, - }, - }) - - exitToast(exit) - .onSuccess((result) => { - setCreatedWebhook({ - token: result.token, - webhookUrl: result.webhookUrl, - }) - onSuccess?.() - }) - .successMessage("Webhook created successfully") - .onErrorTag("ChannelNotFoundError", () => ({ - title: "Channel not found", - description: "This channel may have been deleted.", - isRetryable: false, - })) - .run() - }, - }) - - const handleDismissToken = () => { - setCreatedWebhook(null) - setIsExpanded(false) - form.reset() - } - - if (createdWebhook) { - return ( - - ) - } - - if (!isExpanded) { - return ( - - ) - } - - return ( -
-
{ - e.preventDefault() - form.handleSubmit() - }} - className="space-y-4" - > - ( - - - A display name for this webhook - field.handleChange(e.target.value)} - onBlur={field.handleBlur} - aria-invalid={!!field.state.meta.errors?.length} - /> - {field.state.meta.errors?.[0] && ( - {field.state.meta.errors[0].message} - )} - - )} - /> - - ( - - - Optional description for this webhook -