modernize UI, add dark mode, in-popup settings, auto-sync, animated icons & toasts#113
Open
megyland wants to merge 10 commits intodudor:mainfrom
Open
modernize UI, add dark mode, in-popup settings, auto-sync, animated icons & toasts#113megyland wants to merge 10 commits intodudor:mainfrom
megyland wants to merge 10 commits intodudor:mainfrom
Conversation
- Bookmark events trigger a debounced auto-upload (2s) when auto-sync is enabled - A browser alarm polls GitHub periodically to detect remote changes and auto-downloads - Sync loop prevention via curOperType guard and lastRemoteUpdate timestamp comparison - Skip auto-download if local unsaved changes are pending (badge shows '!') - Differentiated notifications: 'Auto Sync - Upload/Download' vs manual titles - New settings: autoSync toggle and autoSyncInterval (minutes) in options page - Fix alarms.create error: coerce autoSyncInterval to Number before passing - Add alarms permission to manifest - Pin @vitejs/plugin-react to ^5 for Vite 6 compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix missing await on .text() for truncated gist downloads (services.ts)
- Fix wrong cache header name: 'cache' -> 'Cache-Control' (http.ts)
- Eliminate redundant GET /gists/:id call in checkAndAutoDownload: get() now
returns { content, updatedAt } so the check and download share one API call;
downloadBookmarks() accepts prefetched data and returns updatedAt; removes
getUpdatedAt() method entirely
- Fix curOperType getting stuck at SYNC on error: add .catch()/.finally() to
all message handler promise chains so curOperType always resets to NONE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New theme.css with CSS variable overrides for dark mode (Bootstrap 4 compatible: dropdown, form controls, badges, switches, buttons) - New theme.ts utility: applyTheme() handles system preference via matchMedia listener, or sets data-theme attribute directly; useTheme() hook reads setting from storage and applies on mount - Both popup and options pages call useTheme() on mount - Theme selector (System/Light/Dark) added to options page; applies immediately on change without requiring a page reload - Default is 'system', respecting the OS color scheme preference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New [data-theme="black"] variables: pure #000000 background, near-black secondary surfaces, to maximize OLED pixel savings - All dark mode CSS rules extended to cover both dark and black themes - ThemeValue type updated to include 'black' - 'Black (OLED)' option added to the theme selector in settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setup:
- Add Tailwind CSS v3 with postcss.config.cjs + tailwind.config.cjs
- darkMode: ['class', '[data-theme="dark"]', '[data-theme="black"]'] so
existing theme.ts needs no changes
- globals.css defines shadcn CSS variables for light / dark / black themes
- src/lib/utils.ts adds cn() helper (clsx + tailwind-merge)
Components (src/components/ui/):
- button.tsx, input.tsx, label.tsx, switch.tsx, separator.tsx
- button-group.tsx — segmented control used for theme selection
Popup:
- Replaced Bootstrap Dropdown with a clean Tailwind flex column
- Lucide icons (CloudUpload, CloudDownload, Trash2, Settings, Github…)
- Per-button loading spinner during async operations
- Local/remote counts in footer with HardDrive/Cloud icons
Options page:
- Replaced syncForm/react-hook-form with controlled React state
- Saves via browser.storage.sync.set({ options_sync }) on every change
- Theme selection uses ButtonGroup segmented control (System/Light/Dark/Black)
- Auto Sync interval field only shown when Auto Sync is enabled
Removed: bootstrap, react-bootstrap, react-hook-form, react-icons
Bundle: 500 kB → 319 kB (-36%)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace settings button (opened new tab) with full in-popup settings view using React state navigation (back arrow to return to main view) - Increase popup width to 18rem to fit 4-option theme ButtonGroup - Make ButtonGroup buttons flex-1 so they fill container width - Increase icon spinner arc radius from 0.38 → 0.60 for better visibility - Fix options not persisting: saveOptions was writing to 'options_sync' but webext-options-sync reads from 'options'; switch both options.tsx and popup.tsx to use optionsStorage.set() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upgrade wxt 0.19 → 0.20.19 (supports Vite 8 peer dep) - Add vite ^8.0.0 as explicit devDependency - Upgrade @vitejs/plugin-react 5.x → 6.0.1 (requires Vite 8) - Upgrade @wxt-dev/module-react 1.1 → 1.2.2 and auto-icons 1.0 → 1.1.1 - Add publicDir: 'src/public' to wxt.config.ts — WXT 0.20 resolves publicDir from project root instead of srcDir, so _locales were missing from the build output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace static lucide icons with lucide-animated motion icons (cloud-upload, cloud-download, delete, settings, arrow-left, bookmark, github, arrow-up-right); animations trigger on hover of the containing button via imperative refs - Disable upload/download and auto-sync toggle when GitHub token or Gist ID are not configured; show contextual hint and "Go to Settings" CTA - Replace sync interval number input with shadcn Slider snapping to predefined values: 5, 10, 15, 30, 60 min - Add Sonner toast notifications for all settings changes (debounced for text fields, immediate with descriptive messages for toggles/slider/theme) and for upload/download/removeAll action results in the popup - Add components.json for shadcn CLI; install motion, sonner, @radix-ui/react-slider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…refresh - Wrap "Remove All Bookmarks" in a shadcn AlertDialog requiring explicit confirmation before executing the destructive action - Fix AlertDialogTrigger asChild nesting: button is now the direct trigger child so text-destructive styling and icon animation are preserved - Refresh local/remote bookmark counts after every action (upload, download, remove all) by extracting refreshCount() and calling it in send()'s finally() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export: serializes bookmark tree to dated JSON file (bookmarks-YYYY-MM-DD.json) via background exportBookmarks handler reusing existing formatBookmarks logic - Import: reads a user-selected JSON file, shows confirmation dialog warning about replacement, then calls background importBookmarks handler which clears and rebuilds the bookmark tree; refreshes counts on completion - Both actions show toast feedback on success/error and share a fileLoading state to prevent concurrent operations - Add animated DownloadIcon and UploadIcon (lucide-animated) with hover animation wired to button mouse events via imperative refs - Fix AlertDialog width overflow in popup: use w-[calc(100vw-2rem)] p-4 so dialogs fit within the 18rem popup with 16px margin on each side Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A comprehensive UX and UI overhaul: new theme system, auto-sync engine, modernized design, animated icons, and toast notifications.
🎨 UI modernization — shadcn/ui + Tailwind CSS
Replaced the Bootstrap-based UI with shadcn/ui
components
and Tailwind CSS for a cleaner, more consistent design system:
Button,Input,Label,Switch,Separator,ButtonGroupprimitives🌙 Theme system (System / Light / Dark / Black)
#000000backgroundprefers-color-schemefor the System option⚙️ In-popup settings page
view,
without opening the full options page
🔧 Build: Vite 8 / WXT 0.20.19
_localesin build output🔄 Auto-sync engine
Two-way automatic sync between local bookmarks and the remote Gist:
Upload on change
onCreate,onChanged,onMoved,onRemoved)trigger a
debounced auto-upload (2s delay) when auto-sync is enabled
!badge on the extension icon indicates pending local changesDownload on remote change
updates
updated_attimestamps — only downloads when the Gist actuallychanged
!)to avoid overwriting unsynced work
Sync safety
curOperTypeguard prevents concurrent sync operationslastRemoteUpdatetimestamp stored locally to avoid redundant downloadscurOperTypeviafinally()toprevent
the sync from getting stuck
titles
Settings added:
autoSynctoggle +autoSyncInterval(minutes)Bug fixes (code review)
awaiton.text()for truncated gist downloads'cache'→'Cache-Control'GET /gists/:idcall incheckAndAutoDownload—get()now returns{ content, updatedAt }so check and download shareone API call
curOperTypegetting stuck atSYNCon error🔒 Gist configuration guards
ID
are not configured; replaced by a contextual prompt with a "Go to
Settings" CTA
are filled in
✨ Animated icons (lucide-animated + Motion)
Replaced static Lucide icons with animated counterparts from
lucide-animated. Animations trigger on
hover of the
containing button via imperative refs:
🎚️ Sync interval slider
Replaced the free-form number input with a shadcn Slider snapping to
predefined values:
5 · 10 · 15 · 30 · 60min, with tick labels and aninline
current-value display.
🔔 Toast notifications (Sonner)
"Settings saved"(debounced 800ms)"Notifications enabled/disabled""Auto sync enabled/disabled""Sync interval set to X min""Theme set to X"The
Toasterderives its theme from thedata-themeattribute via aMutationObserver, staying in sync with all four theme options.🗑️ Remove All — confirmation dialog
AlertDialogrequiringexplicit
confirmation before executing, preventing accidental data loss
AlertDialogTrigger asChildnesting so the button retains itsdestructive red styling and icon animation
now update immediately after upload, download, and remove all
📁 Export & Import bookmarks to/from file
Export
(
bookmarks-YYYY-MM-DD.json) using the same format as Gist syncImport
that it will replace all current bookmarks
.jsononly); the file is read,validated, and sent to the background which clears and rebuilds the
bookmark tree
Both actions show toast feedback on success or error, and disable each
other while in progress to prevent concurrent operations.
Animated
DownloadIcon/UploadIcon(lucide-animated) with hoveranimations wired to button mouse events.
Dependencies added
tailwindcss,postcss— utility-first CSSshadcn/uicomponent primitives (@radix-ui/*)motion— powers lucide-animated icon animationssonner— toast notificationslucide-react— base icon setTest plan
prompt, auto sync disabled
unlocks
debounce, red
!badge appearsinterval, notification shown
!badge) → auto-download is skipped topreserve local state
width
matches theme
_localesincludednothing, confirm removes bookmarks
structure
disabled