{ if (e.target === e.currentTarget) close(); }} onkeydown={(e) => { if (e.key === "Escape") close(); }}>

Settings

{#each TABS as t} {/each}

{TABS.find((t) => t.id === tab)?.label}

{#if tab === "general"}

Interface Scale

updateSettings({ uiScale: Number(e.currentTarget.value) })} class="scale-slider" /> { const n = parseInt(e.currentTarget.value, 10); if (!isNaN(n) && n >= 50 && n <= 200) updateSettings({ uiScale: n }); }} onblur={(e) => { const n = parseInt(e.currentTarget.value, 10); if (isNaN(n) || n < 50) { updateSettings({ uiScale: 50 }); e.currentTarget.value = "50"; } else if (n > 200) { updateSettings({ uiScale: 200 }); e.currentTarget.value = "200"; } }} /> %

{#each [50,60,70,80,90,100,110,125,150,175,200] as v} {/each}

Server

Server URLBase URL of your Suwayomi instance
updateSettings({ serverUrl: e.currentTarget.value })} placeholder="http://localhost:4567" spellcheck="false" />

Inactivity

Idle screen timeoutShow the Moku idle splash after this much inactivity.
{#if selectOpen === "idle-timeout"}
{#each [["0","Never"],["1","1 minute"],["2","2 minutes"],["5","5 minutes"],["10","10 minutes"],["15","15 minutes"],["30","30 minutes"]] as [v, l]} {/each}
{/if}
{:else if tab === "appearance"}

Theme

{#each THEMES as theme} {@const active = (store.settings.theme ?? "dark") === theme.id} {/each} {#each store.settings.customThemes ?? [] as custom} {@const active = store.settings.theme === custom.id}
{#if active}✓{/if}
{/each}
{:else if tab === "reader"}

Page Layout

Default layoutHow chapters open by default
{#if selectOpen === "page-style"}
{#each [["single","Single page"],["longstrip","Long strip"]] as [v, l]} {/each}
{/if}
Reading directionLeft-to-right for most manga, right-to-left for Japanese
{#if selectOpen === "reading-dir"}
{#each [["ltr","Left to right"],["rtl","Right to left"]] as [v, l]} {/each}
{/if}

Fit & Zoom

Default fit modeHow pages are sized to fit the screen
{#if selectOpen === "fit-mode"}
{#each [["width","Fit width"],["height","Fit height"],["screen","Fit screen"],["original","Original (1:1)"]] as [v, l]} {/each}
{/if}
Max page widthPixel cap for fit-width mode.
{store.settings.maxPageWidth ?? 900}px

Behaviour

{#if !(store.settings.autoNextChapter ?? false)} {/if}
Pages to preloadImages loaded ahead of the current page
{store.settings.preloadPages}
{:else if tab === "library"}

Display

Chapters

Default sort direction
{#if selectOpen === "sort-dir"}
{#each [["desc","Newest first"],["asc","Oldest first"]] as [v, l]} {/each}
{/if}

History

Reading store.history{store.history.length} entries stored
Full data cleanse Removes store.history, stats, completed list, hero pins, and manga links
{:else if tab === "performance"}

Render Limit

Items per page Library and Search render this many items before showing a "Load more" button. Lower = faster scrolling on large libraries.
{store.settings.renderLimit ?? 48}

{#each [12, 24, 48, 96, 200] as v} {/each}

Rendering

Idle / Splash Screen

Interface

Session Cache

Cache entries In-memory request cache for this session (library, sources, genre pages). Cleared on restart.
{perfSnapshot?.cacheEntries ?? 0} entries
{#if perfSnapshot && perfSnapshot.cacheEntries > 0}
Oldest entry
{fmtAge(perfSnapshot.oldestEntryMs)}
Newest entry
{fmtAge(perfSnapshot.newestEntryMs)}
Cached keys {perfSnapshot.cacheKeys.join(", ")}
{/if}
{:else if tab === "keybinds"}

Keyboard shortcuts

Click a binding to rebind, then press the new key combination.

{#each Object.keys(KEYBIND_LABELS) as key} {@const k = key as keyof Keybinds} {@const isListening = listeningKey === k} {@const isDefault = store.settings.keybinds[k] === DEFAULT_KEYBINDS[k]}
{KEYBIND_LABELS[k]}
{/each}
{:else if tab === "storage"}

Disk Usage

{#if storageLoading}

Reading filesystem…

{:else if storageError}

{storageError}

{:else if storageInfo} {@const mangaBytes = storageInfo.manga_bytes} {@const totalBytes = storageInfo.total_bytes} {@const freeBytes = storageInfo.free_bytes} {@const limitGb = store.settings.storageLimitGb ?? null} {@const limitBytes = limitGb !== null ? limitGb * 1024 ** 3 : null} {@const available = mangaBytes + freeBytes} {@const cap = limitBytes !== null ? Math.min(limitBytes, available) : available} {@const pctUsed = cap > 0 ? Math.min(100, (mangaBytes / cap) * 100) : 0}
90} class:warn={pctUsed > 75 && pctUsed <= 90} style="width:{pctUsed}%">
{fmtBytes(mangaBytes)} used {fmtBytes(Math.max(0, cap - mangaBytes))} free
Downloaded manga{fmtBytes(mangaBytes)}
Drive free{fmtBytes(freeBytes)}
Drive total{fmtBytes(totalBytes)}

{storageInfo.path}

{/if}

Cache

Image cacheCached page images stored by the webview

Storage Limit

Limit download storage {store.settings.storageLimitGb === null ? "No limit — uses full drive capacity" : `Warn when downloads exceed ${store.settings.storageLimitGb} GB`}
{#if store.settings.storageLimitGb === null} {:else}
{ const n = parseFloat(e.currentTarget.value); if (!isNaN(n) && n > 0) updateSettings({ storageLimitGb: n }); }} /> GB
{/if}
{:else if tab === "folders"}

Manage Folders

Assign manga to folders from the series detail page.

e.key === "Enter" && createFolder()} style="flex:1;width:auto" />
{#if store.settings.folders.length === 0}

No folders yet. Create one above.

{:else}
{#each store.settings.folders as folder}
{#if editingId === folder.id} { if (e.key === "Enter") commitEdit(); if (e.key === "Escape") editingId = null; }} onblur={commitEdit} style="flex:1;width:auto" use:focusInput /> {:else} {folder.name} {folder.mangaIds.length} manga {/if}
{/each}
{/if}
{:else if tab === "tracking"}

Connected Trackers

Log in to sync your reading progress with external tracking services. After connecting, use the Tracking panel inside any manga's detail page.

{#if trackersError}
{trackersError}
{/if} {#if trackersLoading}

Loading trackers…

{:else}
{#each trackers as tracker}
{tracker.name}
{tracker.name} {tracker.isLoggedIn ? "Connected" : "Not connected"}
{#if tracker.isLoggedIn}
{:else if oauthTrackerId === tracker.id}

Your browser opened the {tracker.name} login page. After authorising, you'll land on a Suwayomi page — copy the full URL from your browser's address bar (it starts with https://suwayomi.org/... and contains your token) and paste it below.

{ if (e.key === "Enter") submitOAuth(); if (e.key === "Escape") cancelOAuth(); }} use:focusEl />
{:else if credsTrackerId === tracker.id}
e.key === "Escape" && cancelCredentials()} use:focusEl /> { if (e.key === "Enter") submitCredentials(); if (e.key === "Escape") cancelCredentials(); }} />
{:else} {/if}
{/each}
{/if}
{:else if tab === "security"}
{#if secError}
{secError}
{/if}

Server Authentication

{store.settings.serverAuthEnabled ? "Enabled" : "Disabled"}
Username
Password
{#if store.settings.serverAuthEnabled} {/if}

App Lock

{#if store.settings.appLockEnabled}
PIN 4–8 digits
{ pinInput = e.currentTarget.value.replace(/\D/g, "").slice(0, 8); pinError = ""; }} onkeydown={(e) => e.key === "Enter" && commitPin()} placeholder="••••" autocomplete="off" />
{#if pinError}{pinError}{/if}
{/if}

SOCKS Proxy

{#if socksEnabled}
Version
{#if selectOpen === "socks-ver"}
{#each [[4,"SOCKS4"],[5,"SOCKS5"]] as [v, l]} {/each}
{/if}
Host
Port
Username Optional
Password Optional
{/if}

FlareSolverr

{#if flareEnabled}
URL FlareSolverr instance address
Timeout Max wait per request, in seconds
{flareTimeout}s
Session name Reuse browser session across requests
Session TTL Minutes before session is refreshed
{flareTtl}m
{/if}
{:else if tab === "about"}

Moku

A manga reader frontend for Suwayomi / Tachidesk.

Built with Tauri + Svelte.

Version

Installed v{appVersion}
{#if onLatestVersion}
✓ You're on the latest version.
{/if} {#if updatePhase === "downloading" && IS_WINDOWS}
Downloading {targetTag ?? "update"}… {fmtProgress()}
{/if} {#if updatePhase === "ready"}
{targetTag} downloaded — restart to finish installing.
{/if} {#if updatePhase === "error"}
{updateError}
{/if}

Releases

{#if releasesError}

{releasesError}

{:else if releasesLoading}

Fetching releases…

{:else if releases.length === 0}

No releases found.

{:else}
{#each releases as release} {@const isCurrent = isCurrentVersion(release.tag_name)} {@const isExpanded = expandedTag === release.tag_name} {@const isTarget = targetTag === release.tag_name} {@const isInstalling = isTarget && updatePhase === "downloading"}
{release.tag_name} {#if isCurrent} installed {/if} {#if release.published_at} {fmtDate(release.published_at)} {/if}
{#if release.body.trim()} {/if} {#if !isCurrent} {#if IS_WINDOWS} {:else} {/if} {/if}
{#if isExpanded && release.body.trim()}
{release.body.trim()}
{/if}
{/each}
{/if}

Links

GitHub → Discord →
{:else if tab === "devtools"}

Splash Screen

Preview idle screenShow the idle splash — dismiss with any click or key

Build Info

Mode: {import.meta.env.MODE}

{/if}