Skip to content

modernize UI, add dark mode, in-popup settings, auto-sync, animated icons & toasts#113

Open
megyland wants to merge 10 commits intodudor:mainfrom
megyland:main
Open

modernize UI, add dark mode, in-popup settings, auto-sync, animated icons & toasts#113
megyland wants to merge 10 commits intodudor:mainfrom
megyland:main

Conversation

@megyland
Copy link

@megyland megyland commented Mar 15, 2026

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:

  • Rebuilt popup and options page using Button, Input, Label,
    Switch,
    Separator, ButtonGroup primitives
  • Removed Bootstrap dependency entirely

🌙 Theme system (System / Light / Dark / Black)

  • Added dark mode with full CSS variable theming
  • Added a Black (OLED) variant with true #000000 background
  • Theme selection persists via settings and applied on extension load
  • Supports prefers-color-scheme for the System option

⚙️ In-popup settings page

  • Settings are now accessible directly inside the popup via a slide-in
    view,
    without opening the full options page
  • Back navigation via an animated arrow button

🔧 Build: Vite 8 / WXT 0.20.19

  • Migrated to WXT 0.20.19 with Vite 8; fixed missing _locales in build output

🔄 Auto-sync engine

Two-way automatic sync between local bookmarks and the remote Gist:

Upload on change

  • Bookmark events (onCreate, onChanged, onMoved, onRemoved)
    trigger a
    debounced auto-upload (2s delay) when auto-sync is enabled
  • A red ! badge on the extension icon indicates pending local changes

Download on remote change

  • A browser alarm polls GitHub at a configurable interval to detect remote
    updates
  • Compares updated_at timestamps — only downloads when the Gist actually
    changed
  • Skips auto-download if local unsaved changes are pending (badge shows
    !)
    to avoid overwriting unsynced work

Sync safety

  • curOperType guard prevents concurrent sync operations
  • lastRemoteUpdate timestamp stored locally to avoid redundant downloads
  • Upload/download failures always reset curOperType via finally() to
    prevent
    the sync from getting stuck
  • Differentiated notifications: Auto Sync — Upload/Download vs manual
    titles

Settings added: autoSync toggle + autoSyncInterval (minutes)

Bug fixes (code review)

  • Fixed missing await on .text() for truncated gist downloads
  • Fixed wrong cache header: 'cache''Cache-Control'
  • Eliminated redundant GET /gists/:id call in checkAndAutoDownload
    get() now returns { content, updatedAt } so check and download share
    one API call
  • Fixed curOperType getting stuck at SYNC on error

🔒 Gist configuration guards

  • Upload and Download buttons are hidden when GitHub token or Gist
    ID
    are not configured; replaced by a contextual prompt with a "Go to
    Settings"
    CTA
  • Auto Sync toggle is disabled with a helper message until both fields
    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:

Icon Animation
CloudUpload Arrow bounces up
CloudDownload Arrow bounces down
Delete (trash) Lid lifts
Settings Gear rotates 180° with spring physics
ArrowLeft Arrow slides, line shortens
Bookmark Squash-and-stretch bounce
Github Body redraws, tail wags on loop
ArrowUpRight Used for "Get Token" external link

🎚️ Sync interval slider

Replaced the free-form number input with a shadcn Slider snapping to
predefined values: 5 · 10 · 15 · 30 · 60 min, with tick labels and an
inline
current-value display.

🔔 Toast notifications (Sonner)

Trigger Toast
Typing token / Gist ID / filename "Settings saved" (debounced 800ms)
Toggle notifications "Notifications enabled/disabled"
Toggle auto sync "Auto sync enabled/disabled"
Move sync interval slider "Sync interval set to X min"
Switch theme "Theme set to X"
Upload / Download / Remove all Success or error message

The Toaster derives its theme from the data-theme attribute via a
MutationObserver, staying in sync with all four theme options.

🗑️ Remove All — confirmation dialog

  • Clicking "Remove All Bookmarks" now opens an AlertDialog requiring
    explicit
    confirmation before executing, preventing accidental data loss
  • Fixed AlertDialogTrigger asChild nesting so the button retains its
    destructive red styling and icon animation
  • Fixed local/remote bookmark counts not refreshing after actions — counts
    now update immediately after upload, download, and remove all

📁 Export & Import bookmarks to/from file

Export

  • Serializes the full bookmark tree to a dated JSON file
    (bookmarks-YYYY-MM-DD.json) using the same format as Gist sync
  • Triggered from a new "Export to file" button in the popup

Import

  • Clicking "Import from file" first shows a confirmation dialog warning
    that it will replace all current bookmarks
  • On confirm, opens a file picker (.json only); the file is read,
    validated, and sent to the background which clears and rebuilds the
    bookmark tree
  • Bookmark counts refresh automatically on completion

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 hover
animations wired to button mouse events.


Dependencies added

  • tailwindcss, postcss — utility-first CSS
  • shadcn/ui component primitives (@radix-ui/*)
  • motion — powers lucide-animated icon animations
  • sonner — toast notifications
  • lucide-react — base icon set

Test plan

  • No token/Gist ID configured → upload/download replaced by setup
    prompt, auto sync disabled
  • Fill in token + Gist ID → upload/download appear, auto sync toggle
    unlocks
  • Enable auto sync → bookmark change triggers upload after 2s
    debounce, red ! badge appears
  • Remote Gist updated externally → auto-download fires at next
    interval, notification shown
  • Local unsaved changes (! badge) → auto-download is skipped to
    preserve local state
  • Sync interval slider snaps to defined values, toast fires on change
  • Hover each action button → icon animates across the full button
    width
  • Switch between all 4 themes → applied immediately, Sonner toast
    matches theme
  • Upload / download / remove all → result toast appears in popup
  • Build produces valid extension with _locales included
  • Click "Remove All" → confirmation dialog appears, cancel does
    nothing, confirm removes bookmarks
  • Export → JSON file downloaded with today's date, valid bookmark
    structure
  • Import valid JSON → bookmarks replaced, count updates, success toast
  • Import invalid/corrupt JSON → error toast shown, bookmarks unchanged
  • Export and Import while other action is loading → buttons are
    disabled

megyland and others added 8 commits March 15, 2026 22:41
- 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>
@megyland megyland changed the title add auto-sync: upload on bookmark change, download on remote change modernize UI, add dark mode, in-popup settings, auto-sync, animated icons & toasts Mar 16, 2026
megyland and others added 2 commits March 16, 2026 11:06
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant