Skimmable phased plan for getting AniTrack onto Android for personal
sideload use (no Play Store, no official F-Droid). Written for both the
maintainer and as working context for AI-assisted implementation.
Covers: desktop v3 parity as a mechanical step (main.go, runtime call
mapping, wailsjs import rewrite), per-file Android storage shims
(keyring on desktop vs EncryptedSharedPreferences via
Android.Secure*), the localhost :6734 to anitrack:// deep-link OAuth
redesign with per-provider dashboard changes, and the sideload run/
package commands plus Obtainium updates. Includes guardrails: small
diffs, per-service file ownership, build-tagged platform files only,
runnable check per phase, and open decisions (first provider, desktop
parallel builds, redirect URI registration).
Stop ignoring keyring.Open failures in all three login files. A failed
Open previously left a nil ring behind the blank identifier, so the next
Get/Set panicked with no message. Each service now opens its ring in
init, logs a service-prefixed warning when storage is unavailable, and
guards every use with a Ready check that fails safe to logged-out.
- AniListUserFunctions.go: add aniRingReady/aniRingSet helpers, log
Open and per-key Set failures, reject invalid ExpiresIn instead of
silently storing 0, clear in-memory JWT on logout even when storage
is missing
- MALUserFunctions.go: add malRingReady/malRingSet helpers covering
login, OAuth callback, and token-refresh saves; same Open/Set/
ExpiresIn/logout treatment
- SimklUserFunctions.go: add simklRingReady/simklRingSet helpers;
same Open/Set/logout treatment
No wallet, key names, or login flow changed. Same ServiceName
AniTrack, same keys, same OAuth callback behavior.
Local flow is now ./release 1.6.7 (or make release VERSION=1.6.7):
bump wails.json productVersion, commit the bump, generate git-cliff
notes into an annotated plain-version tag, and push commit plus tag.
The tag push fires the new Gitea Actions workflow.
- cliff.toml: git-cliff groups matching Bookhoard, tag_pattern for
plain versions so --latest scopes correctly.
- Makefile release target: strict plain-semver validation, dirty-tree
and existing-tag guards, python3 wails.json bump committed as
chore(release), throwaway-tag notes generation, push of main + tag.
- release: wrapper normalizing v/AniTrack- prefixes to plain versions.
- .gitea/workflows/release.yml: version guard (wails.json vs tag,
base-match for -suffix pre-releases), Go 1.25 / Node 20 / Wails
v2.15.0 + webkit2_41 system deps, environment.go from Actions
secrets, make build, AniTrack-<version>.tar.gz packaged from
build/ (bin, icon, desktop, install script, README), idempotent
Release create/update named AniTrack-<version> with archive
attached via REGISTRY_TOKEN PAT.
AniListSearch hardcoded false for the authenticated flag to
AniListQuery, forcing anonymous searches even with a valid AniList
session. Pass a.CheckIfAniListLoggedIn() instead so the stored token
is attached when available, enabling user-specific data and
authenticated rate limits while keeping anonymous search as fallback.
In CheckIfMyAnimeListLoggedIn and MyAnimeListLogin the keyring lookups
assigned MyAnimeListAccessToken to refreshToken and
MyAnimeListRefreshToken to accessToken. Downstream code checks
len(accessToken.Data) for login state and assigns
accessToken.Data -> myAnimeListJwt.AccessToken, so the swap gated
login on the refresh token and stored each token in the wrong field.
Swap the variable names to match their keyring keys so login detection,
JWT restoration, and the empty-token fallback in MyAnimeListLogin use
the correct values.
The search dropdown toggled blindly before the async call settled
and only handled success, so API failures left a blank dropdown
that looked like search did nothing.
- Replace toggle with explicit open/close, Escape and outside-click
to dismiss, and disabled search button while a request is in flight
- Add isSearching/searchError/hasSearched states: loading text,
failure panel with message plus Retry, empty prompt, and a
no-results message for the searched term
- Guard null coverImage/title with a romaji/native fallback and a
keyed each block; ignore stale late responses via request ids
- Tolerate slow AniList days: slow notice at 8s, 30s Promise.race
fail-safe (backend allows 20s), and an immediate offline message
via navigator.onLine so the UI never spins indefinitely
AniListQuery previously ignored json.Marshal/http.NewRequest errors,
logged the wrong variable on client.Do failure, and dereferenced a
nil response body, which could panic and leave callers hanging.
- Return early with user-safe messages on encode/request failures
- Add 20s http.Client timeout and nil guards for res/res.Body
- Keep logs generic so no auth material is ever printed
- Add aniListGraphQLErrorMessage/aniListSearchStatusError helpers
mapping 429 to a rate-limit retry message, 5xx to a temporary
outage message, and GraphQL errors[] (even on HTTP 200) to a
surfaced message
- AniListSearch rejects empty bodies and unparseable payloads with
actionable errors instead of failing silently downstream
The 1.6.1 fix exposed a latent decoding bug: AniList returns
airingSchedule.nodes and relations.nodes as arrays, but the Go Media
struct declared Nodes as single structs. Every full-media response
failed json.Unmarshal partway through - the update path turned that
into a rejected promise which skipped the MAL and Simkl syncs and all
table updates, while page loads silently continued with partially
decoded data (the reason the old tags/genres copy workaround existed).
- Media.AiringSchedule.Nodes is now []AiringScheduleNode and
Media.Relations is an exported []MediaRelation (previously an
unexported field silently dropped by encoding/json); title and
fuzzy-date sub-structs promoted to named types.
- Regenerate wailsjs models for the new shapes.
- Anime.svelte: handleSubmit and deleteEntries now wrap each service
in its own try/catch surfaced via setApiError/ErrorModal, so one
service failing can no longer skip the others; removed the obsolete
tags/genres copy workaround.
- AniListUpdateEntry/AniListDeleteEntry log HTTP status and response
body on failure for terminal diagnostics.
Bump productVersion to 1.6.2.
The AniListUpdateEntry mutation was mangled in 54c109a when the
standardized media field block was pasted in without the media { }
wrapper: media-level fields (idMal, title, ...) sat directly on
SaveMediaListEntry (which returns MediaList), the MediaList fields
(status, startedAt, ..., user) ended up at the Mutation root, and a
stray closing brace made the document a guaranteed 400. Because the
response status was discarded, the frontend received a zeroed
AniListGetSingleAnime and blanked the anime page after every submit,
making AniList appear logged out (and the change was never saved).
- Restore the mutation to match the tested bruno request: id/mediaId/
userId, standard media block inside media { }, MediaList fields
inside the selection, balanced braces.
- AniListUpdateEntry and AniListDeleteEntry now return an error on
403/non-200/unparseable responses (mirroring
GetAniListUserWatchingList) instead of silently returning zero
values.
- Anime.svelte guards against replacing the page data with an empty
response and raises the API error modal instead.
Bump productVersion to 1.6.1.
When the app starts it verifies all three services sequentially, which
could leave the other two login controls looking idle or trigger login
actions before their real state is known. Track which service is
currently being checked and reflect it in the header:
- Add a serviceLoggingIn store (plus setServiceLoggingIn and
isServiceLoggingIn helpers) to GlobalVariablesAndHelperFunctions.
- Have the loginTo* functions and the startup CheckIf* functions toggle
their service in the store (seed all three at startup, clear each as
it resolves).
- In Header, the login buttons show a spinner with 'Checking AniList' /
'Checking MAL' / 'Checking Simkl' and are disabled while that service
is being checked, reverting to the normal 'AniList Login' style label
once its state is known.
- In the avatar menu, the 'Login to X' row shows a spinner with the
matching 'Checking X' text while that service is logging in.
Two MAL login issues were caused by the refresh path being conflated
with the full browser OAuth flow:
1. A spurious 'It is now safe to close your browser tab' dialog was
shown after a *silent* background token refresh, even though the
browser was never opened. The dialog now belongs only to the
browser-callback handler (handleMyAnimeListCallback); the refresh
raises no dialog.
2. When both the access token and the refresh token were invalid, the
app reported MAL as logged in without a username. Now, if a refresh
fails (HttpClient error, non-2xx, or an empty access token), the
stale tokens are cleared and a fresh browser OAuth login is
automatically initiated instead of silently returning an empty user.
refreshMyAnimeListAuthorizationToken now returns a bool indicating
whether a fresh access token was actually obtained, letting
GetMyAnimeListLoggedInUser decide between retrying with the refreshed
token or falling back to a full re-login.
Ignore compiled binaries and packaging archives that are generated
during a build, without hiding tracked build source files:
- build/bin: the compiled application binary
- *.tar / *.tar.gz: packaging archives (e.g. Wails .ptmp temp dirs)
- /AniTrack: the root app binary (fix the previous './AniTrack'
pattern, which gitignore did not match)
Re-run the Wails binding generator so the frontend reflects the browse
Go types that were added in earlier commits (AniListBrowse plus the
Media / MediaList model refinements):
- App.d.ts / App.js: expose the new AniListBrowse() method
- models.ts: add the Media model, the anonymous tags element type,
and type MediaList.media as Media instead of a generic object
Reinstall the Go toolchain and rebuild wails against v2.15.0, then run
'go mod tidy' to update the dependency graph:
- github.com/wailsapp/wails/v2 v2.12.0 -> v2.15.0
- golang.org/x/crypto v0.52.0 -> v0.53.0
- golang.org/x/net v0.55.0 -> v0.56.0
- golang.org/x/sys v0.45.0 -> v0.46.0
- golang.org/x/term v0.43.0 -> v0.44.0
- golang.org/x/text v0.37.0 -> v0.39.0
The regenerated Wails runtime bindings (runtime.d.ts / runtime.js) are
included as they surface new APIs shipped in v2.15: the cross-platform
notification API (InitializeNotifications, SendNotification, etc.) and
the EventsOffAll event helper.
- Bump Go toolchain from 1.24.0 to 1.25.0
- Upgrade Wails from v2.10.1 to v2.12.0
- Upgrade tidwall/gjson from v1.18.0 to v1.19.0
- Upgrade labstack/echo from v4.13.3 to v4.15.2
- Upgrade labstack/gommon from v0.4.2 to v0.5.0
- Upgrade samber/lo from v1.49.1 to v1.53.0
- Upgrade gorilla/websocket to v1.5.3 (new transitive dep)
- Upgrade go-webview2 from v1.0.19 to v1.0.23
- Upgrade danieljoos/wincred from v1.2.2 to v1.2.3
- Upgrade godbus/dbus/v5 from v5.1.0 to v5.2.2
- Upgrade jchv/go-winloader to latest
- Upgrade mattn/go-isatty from v0.0.20 to v0.0.22
- Upgrade tidwall/match from v1.1.1 to v1.2.0
- Upgrade golang.org/x/crypto, net, sys, term to latest
- Add go-toast/v2 as new transitive dependency from Wails
- Fix tilde expansion: replace quoted '~' paths with $HOME so they
actually resolve to the user's home directory instead of being
treated literally
- Add existence checks before copying files so the script is
idempotent and skips already-installed resources
- Add progress/status echo messages for each installation step
so the user can see what is being done
The project's compiled binary (AniTrack) was not covered by existing
gitignore patterns. Only platform-specific extensions like .exe, .dll,
.so, and .dylib were ignored. Add the bare binary name to prevent the
locally-built executable from appearing as an untracked file.
Auto-generated by wails generate module. The FlexString custom type
produces unconventional namespace names in the generated TypeScript but
does not affect runtime behavior.
The MyAnimeList API inconsistently returns statistics status fields (watching,
completed, on_hold, dropped, plan_to_watch) as quoted strings for non-zero
values (e.g. "8217") but as bare numbers for zero values (e.g. 0). This caused
JSON unmarshal errors for anime with zero counts in any status field.
Introduce a FlexString custom type that implements json.Unmarshaler to accept
both JSON strings and JSON numbers, always storing the result as a string. The
type definition lives in MALTypes.go and the unmarshal logic in MALFunctions.go
to keep static types and behavior separate.
Update productVersion from 1.0.0 to 1.5.0 in Wails project
configuration to reflect the addition of AniList watchlist sorting,
extracted refresh button component, and pagination improvements.
Add comprehensive release notes documenting the first stable release of
AniTrack, including highlights such as webkit2gtk 4.1 Linux builds,
comprehensive error handling across all services, AniList watchlist
sorting, and various UI polish improvements.
Move the refresh button out of the WatchList component header and into
its own RefreshWatchListButton.svelte component, placing it at the top
of the Home route page with right-alignment. This gives the refresh
control better visibility at the page level rather than being buried
inside the watchlist header.
Replace the removed refresh button in the WatchList header with the new
Sort dropdown component, giving users sort controls directly alongside
the watchlist title.
Implement a Sort.svelte component that provides a dropdown menu allowing
users to dynamically reorder their AniList watchlist by various parameters
including media title, score, status, progress, popularity, and dates.
The component binds to the global aniListSort store and fetches updated
watchlist data from the backend whenever the user selects a new sort option,
providing immediate visual feedback.
Wails v2 defaults to linking against webkit2gtk-4.0, but modern Linux
distributions (e.g. Fedora) only ship webkit2gtk-4.1. The webkit2_41
build tag tells Wails to link against the correct library.
Provides two targets:
- make dev: run wails dev with the correct tag
- make build: build the production binary with the correct tag
- make clean: remove build artifacts
Add detailed release notes for the v0.6.5 patch release covering:
- Enhanced button disabled states in Anime and Pagination components
- Fixed media cover image sizing in WatchList
- AvatarMenu service status indicator
- TypeScript type safety fixes in Pagination event handlers
- Code formatting standardization across components
App.svelte:
- Import and render ErrorModal component
- Add ErrorModal to main app layout below Header
CheckIfAniListLoggedInAndLoadWatchList.svelte:
- Import error state helpers (setApiError, clearApiError)
- Wrap LoadAniListUser in try-catch with error handling
- Wrap LoadAniListWatchList in try-catch with error handling
- Update CheckIfAniListLoggedInAndLoadWatchList with error handling
- Remove old alert() calls in favor of modal system
Home.svelte:
- Import isApiDown and apiError stores
- Add conditional rendering for API down state
- Display user-friendly "API Unavailable" message when apiError is set
- Show warning icon and helpful messaging
Error handling is now fully integrated across the frontend application.
Add new ErrorModal component for API error display:
- Auto-displays when apiError store is set
- Shows service name, error message, and status code
- Provides "Retry Connection" button to attempt reconnection
- Provides "Dismiss" button to close modal and continue
- Integrates with all three services (AniList, MAL, Simkl)
- Uses flowbite-svelte Modal and Button components
- Proper Svelte event handling with on:click
Uses Tailwind CSS for styling with red error theme and helpful messaging.
Add centralized error state system:
- New ApiError interface (service, message, statusCode, canRetry)
- apiError writable store for current error state
- isApiDown writable store for API availability status
- setApiError() helper to set error states
- clearApiError() helper to reset error states
Provides reactive error state across entire application.
MALFunctions.go:
- Update MALHelper to return (json.RawMessage, string, error)
- Add network error handling and proper request error checking
- Update GetMyAnimeList to return (MALWatchlist, error)
- Update MyAnimeListUpdate to return (MalListStatus, error)
- Update GetMyAnimeListAnime to return (MALAnime, error)
- Update DeleteMyAnimeListEntry to return (bool, error)
MALUserFunctions.go:
- Replace log.Fatalf with log.Printf in server error handling
- Prevent server shutdown on OAuth callback errors
All MAL API calls now properly propagate errors to frontend.
Add configurable sort functionality to AniList watchlist system:
- Add aniListSort writable store to GlobalVariablesAndHelperFunctions
- Update Pagination component to subscribe to and use dynamic sort parameter
- Refactor CheckIfAniListLoggedInAndLoadWatchList to use sort from store
- Remove hardcoded MediaListSort.UpdatedTimeDesc in favor of configurable sort
- Improve code formatting with arrow functions and consistent spacing
- Add sort parameter to all GetAniListUserWatchingList calls
This allows users to customize their watchlist sorting preference instead of being limited to the default 'updated time descending' sort order.