Desktop implementation
Wails screens.
Desktop bookmarks: a keyboard shortcut (Cmd/Ctrl+D), a persistent left sidebar with a Bookmarks section, and a chip-style affordance ā keyboard-first, screen-rich UX. Same shared Bookmark type, very different presentation.
The API client ā Wails edition
Two options here. Either call the API directly from React (works fine), OR bind a Go function and let Wails handle it. We'll do the React path because it shares the most code with web + mobile.
import { BookmarkSchema, type Bookmark } from '@my-saas/shared'import { authedFetch } from '../api'export async function listBookmarks(): Promise<Bookmark[]> {const res = await authedFetch('/api/bookmarks')const json = await res.json()return json.data.map((b: unknown) => BookmarkSchema.parse(b))}export async function createBookmark(productId: number) {const res = await authedFetch('/api/bookmarks', {method: 'POST',body: JSON.stringify({ product_id: productId }),})return BookmarkSchema.parse((await res.json()).data)}export async function deleteBookmark(id: number) {await authedFetch(`/api/bookmarks/${id}`, { method: 'DELETE' })}
Same hooks ā same React Query
Copy the hooks file from web (or mobile) verbatim. React Query is identical on Wails. This is the third surface using the exact same logic.
The keyboard shortcut
Desktop earns its keep with shortcuts. Cmd/Ctrl+D toggles a bookmark on whatever product is currently selected.
import { useEffect } from 'react'export function useShortcut(combo: { key: string; mod: 'cmd' }, callback: () => void) {useEffect(() => {const onKey = (e: KeyboardEvent) => {const mod = e.metaKey || e.ctrlKeyif (mod && e.key.toLowerCase() === combo.key.toLowerCase()) {e.preventDefault()callback()}}window.addEventListener('keydown', onKey)return () => window.removeEventListener('keydown', onKey)}, [combo.key, callback])}
const toggle = useToggleBookmark(product.id)useShortcut({ key: 'd', mod: 'cmd' }, () => toggle.mutate())
That's the entire shortcut. Show a hint somewhere in the UI so users know it exists (e.g., a kbd label next to the bookmark button).
The persistent sidebar
Desktops have screen real estate. Use it: a left sidebar that shows the user's recent bookmarks as a list. Always visible, click-to-jump.
import { Heart } from 'lucide-react'import { useBookmarks } from '@/hooks/use-bookmarks'import { useProducts } from '@/hooks/use-products'import { Link } from 'react-router-dom'export function BookmarksSidebar() {const { data: bookmarks = [] } = useBookmarks()const { data: products = [] } = useProducts()const items = bookmarks.slice(0, 10).map((b) => products.find((p) => p.id === b.product_id)).filter(Boolean)return (<div className="border-l border-border w-64 p-3 hidden lg:block"><h3 className="text-xs uppercase tracking-wider text-text-muted mb-3 px-2 flex items-center gap-1.5"><Heart className="h-3 w-3" /> Bookmarks</h3><div className="space-y-1">{items.length === 0 && (<p className="text-xs text-text-muted px-2">āD on any product to save.</p>)}{items.map((p) => (<Linkkey={p!.id}to={"/products/" + p!.id}className="block px-2 py-1.5 rounded text-sm text-text-secondary hover:bg-bg-hover hover:text-foreground truncate">{p!.name}</Link>))}</div></div>)}
Mount it in your AppLayout on the right. Users get an always-visible bookmark list. Mobile would never have room for this; web could but desktops earn it most.
The bookmark chip
Instead of a tiny heart icon, desktop uses a labeled chip with the keyboard hint baked in:
import { Heart } from 'lucide-react'import { useBookmarks, useToggleBookmark } from '@/hooks/use-bookmarks'import { cn } from '@/lib/utils'export function BookmarkChip({ productId }: { productId: number }) {const { data: bookmarks = [] } = useBookmarks()const toggle = useToggleBookmark(productId)const isMarked = bookmarks.some((b) => b.product_id === productId)return (<buttononClick={() => toggle.mutate()}className={cn('inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs',isMarked? 'border-rose-500/40 bg-rose-500/10 text-rose-300': 'border-border bg-bg-elevated hover:bg-bg-hover',)}><Heart className={cn('h-3 w-3', isMarked && 'fill-rose-500')} />{isMarked ? 'Bookmarked' : 'Bookmark'}<kbd className="ml-1 text-[10px] opacity-60">āD</kbd></button>)}
The kbd label teaches the shortcut without forcing a tooltip. Power users will pick it up by the third bookmark.
ā on macOS and Ctrl on Windows / Linux. Detect via navigator.platform or your OS hook from chapter 3 of the Desktop course, and render the right kbd glyph.Right-click context menu ā desktop only
Power-user touch: right-click a product ā "Bookmark". Wails supports a custom HTML context menu (or a native one via the runtime).
<divonContextMenu={(e) => {e.preventDefault()showContextMenu({ items: [{ label: 'Bookmark', onClick: () => toggle.mutate() }] })}}>...
Web could do this too but doesn't ā browser users expect their own context menu. On a native app, custom context menus are the norm.
Quick check
Try it
Desktop bookmarks complete:
- Copy the api client + hooks from mobile (verbatim).
- Wire
Cmd/Ctrl+Don the product detail page. - Show the BookmarkChip with kbd hint on every product card.
- Mount BookmarksSidebar in your app layout.
- Test on at least one OS other than your dev machine (Hyper-V Windows VM if you're on Mac, or vice versa) ā confirm the modifier key glyph is right.
By the end, the same bookmark action is reachable three ways: click the chip, āD, right-click ā Bookmark. That's desktop's "multiple ways to do one thing" ethos.
What's next
Chapter 4 ā Sync + Offline. Both mobile and desktop need real offline handling. We'll wire React Query persistence for mobile and the outbox pattern for desktop, then resolve conflicts when two surfaces edit the same record.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled ā suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub