Changelog
All notable changes to this project. Dates in YYYY-MM-DD.
[0.21.1] — 2026-08-25
Fixed
- DeepL config validation cached a permanent failure on 429 — free-tier keys (
:fx) get rate-limited when the form is submitted in bursts (every config save attempt plus every DeepL test click is one live/v2/translatecall; the free tier's limits are tight). The resulting failure was then cached with no TTL, so the exact same config kept being rejected instantly until a restart even though the key was valid. Transient failures (429/5xx/network/timeout) are no longer cached — resubmitting retries live; only successes and permanent errors (401/403 etc.) are cached. The subtitle translation path was never affected (withRetryalready backs off on 429: 2s, 4s).
[0.21.0] — 2026-08-25
Changed
- Subtitle flow: every NATIVE subtitle is listed immediately, translation runs in the background — previously the subtitle list only showed a top-5 labeled with the target language (translate=true/false) and prefetched the translation of the first file. Now:
- Every subtitle found by the search (multi-language v3 + public API) is listed in its original language (
en (native · v3 en),id (native · v3 id), …) — visible in Stremio right away, no waiting for translation. - On play: subtitle language == target → served directly (native). Language != target → the native subtitle is served instantly (even straight from the prefetched raw cache) while a translation to the target language runs in the background (
translateSubtitle, deduped viainflight). The background translate is kicked from both serve paths (cache hit and miss); the source language is derived from the v3 fileId (30949:en→en) with a fallback map from the list handler, so it still works after a server restart without a prior list request. - When the translation finishes, the translated cache is written → a new target-language entry appears in the next subtitle list (
id (translated · v3 en)) — the user just picks it. Verified live (tt0111161): 63 natives listed instantly, background translate ~4 minutes, next list 64 entries, playing the translated one = cache HIT in 15ms. - Raw subtitles from v3 are prefetched into the raw cache while the list is built (direct URL, no OpenSubtitles quota); public-API subtitles are cached lazily on play (5/24h quota per key).
- Every subtitle found by the search (multi-language v3 + public API) is listed in its original language (
[0.20.1] — 2026-08-25
Fixed
- Free-plan quota eaten by FAILED translations (not successes) — previously the free-quota counter was incremented at the cache-miss gate before translating; because failures (GLM/LLM 400) never write cache, re-requesting the same file from Stremio/Smart TV stayed a cache miss → the counter kept climbing while zero translations actually succeeded. A free user could burn all 5 daily slots without a single subtitle translated. The counter is now split into two roles: the gate (
freeQuotaBlocked) only READS usage (rateLimitCheck, no increment), while the increment happens only after a translation successfully writes cache (bumpFreeQuotaviarateLimitHitindoTranslate). Failed translates cost 0 slots; 5 SUCCESSFUL translations per day is what blocks. A stale rate-limit row holding 5 hits from failed translates was deleted (the affected free user is no longer blocked today). - GLM 5.x (e.g.
glm/glm-5.3via the LLM proxy) failed hard with 400 — root cause: GLM 5 models mandate thinking (reasoning_effortmust below/high/max;noneor a missing field is rejected upstream with code 1210). The proxy wraps that 1210 JSON error into an empty HTML page, so (1) the(html-proxy)tag incallLLMstopped the reasoning ladder from ever escalating, and (2) retrying withnonehit 1210 again → failure forever. Two-layer fix: (a) models withglmin the name are detected up front and sendreasoning_effort:"low"from the first attempt (no attempt wasted onnone); (b) if an html-proxy error occurs while we asked fornone, the ladder still escalates tolowonce — HTML can hide a 1210, not just a dead proxy. Verified live:low→ 200 with a correct JSON translation. - Exponential backoff between LLM retries for flaky proxies (html-proxy/network/5xx) — the proxy intermittently answers
400with an HTML page regardless of the request body (the GLM upstream is unstable), so 3 back-to-back retries all fell inside the same bad window and all failed. Infra failures now trigger an exponential sleep (1s, 2s, capped 8s) before the next attempt — the bad window can pass and the next attempt lands in a good one. The per-host circuit breaker only helped pools with >1 provider; a single provider still got retried immediately. Backoff covers both. - Free-plan quota counts only NEW translations, not cache hits — the 0.20.0 gate counted every request (cached or translated), so replaying an already-translated file (Stremio/Smart TV behavior: repeated requests for the same subtitle URL on load/seek/replay) drained the 5 daily slots. Since the product promises "cache hits & BYOK stay free", the quota now only consumes slots on the cache-MISS translate path (download + new translation); cache hits and native subtitles (
s=1) no longer count. The gate moved to the translate path, before download (a blocked user doesn't waste OpenSubtitles download quota).
[0.20.0] — 2026-08-25
Added
- Free-plan quota: 5 subtitles per day — free users (without an active subscription) are limited to 5 served subtitles per UTC day (cached or translated, native or not). The gate lives in
serveSubtitle— one choke point for every path. The 6th+ request receives a valid subtitle cue containing the limit message + a pointer to /pricing (not an HTTP error, so players still load it). The counter uses therate_limitstable (scopesub:<userId>, 86400s window); paid plans and anonymous tokens (no owner) are unlimited.
Fixed
- Plan edit via /admin now adjusts the credit balance — changing a plan (e.g. free → pro) previously only updated the plan column without touching credits, while the official activation path (payment webhook) always grants the new plan's
credits_per_month.POST /admin/user/:idnow applies arenewaltop-up worth the new plan's allocation when the plan actually changes (downgrade to free = 0, no ledger row); existing balance is not clawed back — the ledger stays honest.
[0.19.1] — 2026-08-25
Fixed
- User edit save failed with "no valid fields" — the server only mounted
express.json(); the HTML form sendsapplication/x-www-form-urlencoded, soreq.bodywas empty and the plan/status whitelist always rejected. Addedexpress.urlencoded({ extended: false }).
Added
- /admin: edit user — Users table rows now have an EDIT link to
/admin/user/:id(same admin guard as /admin): change plan (list from the plans table) and status (active/suspended/banned), plus email, created date, credit balance. NewupdateUserin db.js (partial update, unknown user → null); POST validates plan/status against whitelists then redirects back to the Users tab of the originating page.
Changed
- /admin: Users table 10 rows per page — down from 20 to fit the tab panel width.
- /admin: Overview cards always visible — stat cards are no longer hidden behind tabs; tabs are now Users and Usage by Model only, with the overview cards always shown above.
- Tabs on /admin — the admin page is split into tabs: Overview (stat cards), Users (table + pagination), Usage by Model (table + pagination). The active tab is kept in
?tab=(server-rendered, pagination links carry the active tab), switching is instant without reload (tiny JS), and Users/Usage tab titles carry count badges.
Fixed
- VTT with a blank line inside a cue — LLM translations can insert
\n\ninside cue text; in WebVTT a blank line ends the cue, so strict web parsers (Chromium/Firefox<track>in Stremio web) treat the rest of the text as a cue without timing — the track fails to load / chunks go missing (found in 5 of 16 cache files; desktop players are more tolerant so it went unnoticed).toVttnow sanitizes cue bodies: per-line trim + blank lines dropped. - "Save failed: token failed (400)" with no explanation — the frontend discarded the error body from the save call, hiding the real cause (usually
config validation failedwhile the LLM proxy is inside its ~30s circuit-breaker replay window after an error). The server message is now shown inline. Note: since 0.17.2, saving an unchanged config skips the round-trip entirely (frontend signature cache + stable-token fast path), so a 400 only happens when the config changed AND live validation failed — retry after ~30s. - Install URL still flip-flopping on back-and-forth config edits — the content-dedup path of
tokenToCfgran BEFORE the user-stable path: with legacy duplicate rows (pre-0.17.0 race), a config matching old content returned that duplicate token while a new config fell back to the user's newest token — the install URL moved on every model switch. Logged-in users now short-circuit first: the user's own token is always used (same config = fast return without live revalidation; different config = validate then update the same token). Content dedup only applies to anonymous configs. - Boarding check red despite HTTP 200 — the proxy appends an SSE-style
data: [DONE]tail to non-streaming replies, soJSON.parsein the test endpoint failed → content treated as empty → probe reported "no JSON array" although the model answered correctly. The SSE tail is now stripped before parsing (same pattern ascallLLMin translator.js).
Changed
- One stable token per user —
tokenToCfgoriginally registered a new token every time the config changed (dedup by content, not by user), so the install URL/<token>/manifest.jsonchanged on every config edit (made worse by a save+install race that created identical pairs). The user's existing token is now reused: the same install URL stays valid across edits, and a manifest already installed in Stremio follows the latest config without reinstalling.
Added
- Loading states on the configure page — BROWSE models: button disabled + spinner, dialog shows a "Loading models..." row during fetch (previously an empty dialog with no feedback). Boarding check: spinner "Checking..." button disabled while the test runs.
- Boarding check sends a real prompt — the old test was just
max_tokens:5"Say pong" (which passed even for models that then failed every translate batch, and never touched the reasoning path). The probe is now a 1-cue translate batch: the same JSON-array system prompt as the real pipeline, asserts the reply starts with[, sendsreasoning_effort:"low"(catches thinking-mandatory models at install time), 30s timeout. Failures report the cause (httpvsno JSON array in reply).
Fixed
- One subtitle listed twice in Stremio — the handler served every variant as an
.srt+.vttpair; Stremio registers every subtitle object so each file showed up as two rows. Now one entry per variant (.vttonly, the disk cache is VTT anyway), on both the cache-first and no-cache paths. - Player sometimes failed to load
.vtt— two cases inserveSubtitle: (1) an unparseable source (e.g. an expired v3 URL replying HTML) produced a header-only VTT with no cues, which players reject as invalid; now a single full-length error cue is served so it always loads. (2) a download failure (quota 406, URL lost after restart) replied400 text/plainthe player can't parse; the reply is now valid subtitle content containing the error message, consistent with the[Translating...]placeholder pattern.
[0.19.0] — 2026-08-25
Added
- /admin: edit user — Users table rows now have an EDIT link to
/admin/user/:id(same admin guard as /admin): change plan (list from the plans table) and status (active/suspended/banned), plus email, created date, credit balance. NewupdateUserin db.js (partial update, unknown user → null); POST validates plan/status against whitelists then redirects back to the Users tab of the originating page.
[0.15.2] — 2026-08-25
Fixed
- Subtitles cached from another source language didn't show in the Stremio player — the subtitle handler used
searchTiered, whose Tier 1 only returns target-language (native) subs. The cache-first pass then iterated that single-language list only, so preloads/translations made from en/es/… sources on the catalog page were never served (only 1–2 native entries appeared).searchTierednow includesv3All(the full multi-language list) in every tier result, and the cache-first pass iterates that list withfileId|variantdedup — every cached variant (native raw + translated) is served.
[0.15.1] — 2026-08-25
Fixed
- GLM 1210 loop (
This model always engages in thinking and cannot be disabled; please use low, high, or max) — four bugs: (1) the retry strippedreasoning_effortentirely, but GLM-style models require a level (low/high/max), so the retry 400'd again; (2) level detection matched theplease use low, high, or maxhint text, but proxies truncate the upstream error before that phrase — classification now keys oncode 1210+ whether the field is named, never on hint phrases, with escalation ladder"none"→"low"→ stripped; (3) thereasoningOverridemap lived insidetranslateBatch, so every batch re-sentreasoning_effort: "none"and re-learned — it is now truly module-scoped; (4) the proxy opens a circuit breaker on the upstream 400 and replays the cached error for ~30s regardless of body, so the immediate"low"retry got a cached rejection and the ladder wrongly demoted to stripped — the handler now parses(reset after Ns)and waits out the window before retrying (verified by direct probe: after reset,reasoning_effort:"low"returns 200). The reasoning-retry inside the catch no longer propagates out of the retry loop when it fails. - Truncation salvage failed when the cut landed inside a string containing
}— step-3 truncation salvage used a singlelastIndexOf("}"); when the max_tokens cut landed right after a}INSIDE a string value (e.g."-{alarm}"), the closed array was still invalid → the whole batch failed even though the prefix was valid. It now walks backwards from the cut position until a parse succeeds — cues before the cut are salvaged, the rest are filled with original text (playable). - "Cached" badge lost after page reload —
/api/subtitlesrecomputed cache paths ascache/<shard>/butcache.jswrites tocache/<APP_ENV>/<shard>/(defaultproduction), so the existence check always missed for new files and the UI cache status vanished on reload. AddedhasCache(key)incache.jschecking the same ENV-sharded path as writes;/api/subtitlesuses it now. ReferenceError: fileId is not definedin/api/preload—fileIdwas declared insidetryaftermakeConfig; whenmakeConfigthrew, thecatchblock'slogErrorreferenced an out-of-scopefileIdand crashed the error handler.fileIdis now hoisted above thetry.
[0.15.0] — 2026-08-24
Added
- DeepL restricted to paid plans — free tier can no longer select or submit DeepL as the translation provider. The provider dropdown hides the DeepL option for free users,
applyProviderModeforce-resets any restoreddeeplconfig back toopenai, and the backend now rejectsprovider: "deepl"on free plans with403 free_tier_no_deepl(previously only the multi-provider pool was guarded).
Fixed
- Model selection lost on reload — picking a model/provider now persists to
localStorage. The save handler only listened to thechangeevent, but programmatic value sets (pickModel, quick provider, gate inputs) fireinput, notchange, so hidden fields likemodelwere never saved. Both events now trigger a save. - Server config restore fills empty hidden fields — the cross-device restore previously only filled hidden provider fields (baseUrl/apiKey/model) when
localStoragewas completely empty; a partially-populated device never received the missing model. It now fills any hidden field that is still empty, and the persisted snapshot is taken from the current form state instead of a stalelocalsnapshot.
[0.14.1] — 2026-08-24
Fixed
- Config auto-restore across devices — config now restores automatically when you log in from another device. The per-user config binding was previously dead (bug:
reqwas never passed intotokenToCfg), so restore never matched. The binding is fixed; signing in on a fresh device repopulates the configure form + provider gates automatically.
[0.14.0] — 2026-08-24
Added
- Two-row nav (G-row & A-row) — the masthead now shows two menu rows:
G(Configure, Catalog, Stats, Map) andA(Pricing, Dashboard, Admin), each sorted by gate number. The Admin item (A4) only appears forADMIN_EMAILSoperators. - Dashboard & Admin menu items in nav —
/dashboardand/adminare now reachable straight from the masthead (previously there was no menu). - Server-side pagination on
/dashboard&/admin— the Recent usage (dashboard) and Users (admin) tables are now paginated server-side (?p=, default 20) with a flight-board style pager (/stats): Prev/Next + number window + ellipsis, always visible even with a single page. - Pagination for the Usage by Provider / Model tables on
/admin— paginated server-side (?um=, default 25), a second pager under the table. GET /api/usage— JSON endpoint for the logged-in user's usage history (Clerk session), paginated.
Changed
/dashboard&/adminfully server-rendered — previously fetch-JS (/api/admin/overview) + loader; now data & pager render on the server, URL-based (?p=), consistent with/stats&/errors. Pagination buttons always render (Prev/Next disabled at bounds).- Removed the email/plan/upgrade/sign-out rows from
/dashboard— account info moved into the stat cards (Credits/Plan/Translations/Cache hits).
[0.13.0] — 2026-08-23
Changed
- Auth fully via Clerk — the custom scrypt email/password + session cookie (
src/auth.js) was replaced by Clerk:@clerk/express(clerkMiddleware,getAuth,clerkClient).- Removed the custom
/api/register,/api/login,/api/logout— sign-in/sign-up now go through the Clerk UI (Clerk.mountSignInat/login). users.id= Clerk userId (TEXT).password_hashdropped.ensureClerkUser()creates/remaps the DB row on first login (email as the join key for legacy credits/plans data).requirePageAuth/requireAdminusegetAuth(req)+ADMIN_EMAILSviaclerkClient.users.getUser.- The masthead on every page (landing/catalog/stats/map/errors/pages) has
wfAuthSlot— UserButton when signed in, Sign in button for guests.
- Removed the custom
.envis gitignored (Clerk keys must never enter git).
Notes
- Accounts are created in the Clerk Dashboard (not the DB). Set
ADMIN_EMAILSin.envfor/adminaccess. scripts/seed-test-accounts.jsonly seeds plans + placeholder DB rows; passwords/accounts go through Clerk.
[0.12.0] — 2026-08-16
Added
- Platform UI (PRD §3, §12) —
/login(sign-in + register toggle),/pricing(plan cards + sandbox checkout),/dashboard(credits, usage, API key generation),/admin(control tower: users, subs, token totals, usage-by-model;ADMIN_EMAILSonly),/docs(public API reference). All wayfind-styled, no build step. - Test accounts seed (PRD §10) —
seed-test-accounts.js:developer@/supporter@/pro@/[email protected](passwordtestpass123) +free_test/supporter_test/pro_test/power_testplans with starting credits. Idempotent, dev-only (APP_ENV !== productionorFEATURE_TEST_PLANS=true). - Priority queue (PRD §12) — translate jobs queue on
MAX_CONCURRENCY; higher plan priority (plans.priority) is dispatched first. FIFO among equal priority. - Cost tracking (PRD §19) —
translateBatchacceptsonUsage({provider, model, promptTokens, completionTokens}); subtitle + API translates persist token counts inusage. Admin overview exposes token totals + per-model breakdown. - Analytics —
usageByModel()/sumTokens()/listUsers()/countActiveSubscriptions()queries +/api/admin/overview. - DEVELOPMENT banner shared component
wfDevBanner()— now on landing, catalog, stats, errors, map, and all platform pages in dev env.
[0.11.0] — 2026-08-16
Added
- Monetization platform (dev/staging) — PRD "Streamio SRT Monetization & Platform Upgrade". Every feature sits behind feature flags (default off → old production unaffected):
- Env isolation (Phase 0) —
APP_ENV,CACHE_PREFIXenter thecacheKeyhash, DBdata/app_${APP_ENV}.db, cache pathcache/${APP_ENV}/, DEVELOPMENT banner on the landing page. - Identity (Phase 1) —
users/api_keys/plans/subscriptions/credit_ledger/usage/payment_eventstables; email+password auth (scrypt) + session cookie (/api/register,/api/login,/api/logout,/api/me). - Credits (Phase 1) — credit debit in
doTranslate(managed), refund when a batch fails, cache hits free, out-of-credits placeholder. - Managed provider (Phase 2) —
MANAGED_PROVIDERSenv (baseUrl|apiKey|model pool); managed-plan users translate via the operator pool, BYOK users keep using their own key. - Subscription stub (Phase 3) — idempotent
POST /webhooks/payment(event_id UNIQUE),PAYMENT_MODE=sandbox, per-plan credit top-up. - Public API (Phase 4) —
POST /api/v1/translate,GET /api/v1/usage,GET /api/v1/me,POST /api/v1/keys; API keyssk_dev_/sk_live_with env-prefix validation. - Rate limiting (Phase 5) — 60s sliding window per API key (default 60 req/min),
rate_limitstable. - Token usage capture (Phase 5) —
callLLMcapturesusagefrom provider responses for cost tracking.
- Env isolation (Phase 0) —
docker-compose.dev.yml— isolated dev instance (internal port 7001, named volumes, dev env).test-cache-env.js— child-process test: dev cacheKey ≠ prod.
Notes
- All new features default off (
FEATURE_*env). Enable per phase in dev. - No real payment gateway (Midtrans/Xendit) wired yet — the webhook is still a sandbox stub.
[0.10.3] — 2026-08-12
Fixed
- Manifest validation —
provider+includeCatalogselect options nowstring[](regression from earlier change). Stremio rejects[value,label][]inconfig.N.options.M(invalid_type: expected string, received array). Custom labels dropped, values rendered as-is.
[0.10.2] — 2026-08-12
Fixed
- Gemini 400 INVALID_ARGUMENT —
reasoning_effortfield rejected by Gemini with generic error (didn't name the field). Catch now strips field on any 400, retrying without it.reasoning_effortis the only non-standard field sent, so any 400 blaming an unknown arg is treated as it.
[0.10.1] — 2026-08-12
Fixed
- OpenSubtitles quota circuit breaker — public-API
/downloadallows 5/24h per Api-Key. Once exhausted (406 withreset_time_utc), all public-API download calls short-circuit until reset window lapses. Stops hammering OS + error-log spam per Stremio retry. v3 direct-URL path unaffected. - v3 origin log —
searchV3now logs hit count + how many have a direct URL, to diagnose files leaking from v3 result list into the public-API path (which would still 406).
[0.10.0] — 2026-08-07
Added
- Provider pool repeater UI —
/configurepage renders the provider pool with per-gate boarding checks (model + key validation per provider) - Translate-text failover —
/api/translate-textwalks the provider pool on failure instead of single-endpoint abort
[0.9.0] — 2026-08-07
Added
- Multi-provider LLM pool —
openaiProvidersconfig: array of{baseUrl, apiKey, model}. Batches round-robin across providers; on error, failover advances to next provider. Distributes load + isolates one dead provider from killing translation.
[0.8.0] — 2026-08-07
Added
- DeepL provider — dedicated translation path (not OpenAI-compatible). Plain-text array translate (
split_sentences=nonewlines,preserve_formatting). Free (:fx) vs Pro key auto-routed to correct host. Selected viaprovider: deeplconfig.
[0.7.3] — 2026-08-06
Added
- Cerebras — added to quick-pick provider list on
/configure
[0.7.2] — 2026-08-06
Changed
- Concurrency auto-scaling —
batchSize ≤ 20runs batches sequential to avoid bursting past rate-limited provider TPM limits; larger batches use fullMAX_CONCURRENCY
[0.7.1] — 2026-08-06
Fixed
reasoning_effortrejection — providers that reject the field for non-reasoning models (Groq, Cerebras) now caught by error message and retried without it- Missing-cue fallback — LLM skips in large batches now filled with original text instead of dropping the cue (partial result stays playable)
[0.7.0] — 2026-08-04
Added
includeCataloginstall option — user can opt out of Stremio catalog resource (subtitles-only mode). Dummy catalog seed for empty token configs.- Telegram group button in donate card + overlay
Changed
- Bumped version
0.6.7→0.7.0
[0.6.7] — 2026-08-03
Fixed
- 502 → 400 status — Cloudflare intercepts 502 into HTML, breaking client JSON parse (
Unexpected token '<'). All 502s now return 400 with JSON. - Negative cache fileId 406 — stale v3 fileIds rejected by public API (
Invalid file_id) cached 120s so only first Stremio retry hits OS + logs - Transient upstream filter — 469/429/5xx/network failures no longer written to
error_log(only real failures counted) - Catalog detail cast — fixed
[object Object]render in cast list - Map page — flag + country name for all ISO2 codes
/configureredirect → landing page, manifest intercept usesreq.url(encoded), removed doubledecodeURIComponent
Added
- Telegram group donate card
[0.6.6] — 2026-08-03
Changed
- Manifest dynamic anime day names — localized by
targetLang(was hard-coded English)
[0.6.5] — 2026-08-03
Changed
- Anime catalog day names in Bahasa Indonesia (reverted in 0.6.6 to per-user localization)
[0.6.4] — 2026-08-03
Changed
- Anime catalog via TVMaze schedule by day (Kitsu unreliable)
max_tokensclamp 4096 — router model 828 rejects >4096
[0.6.3] — 2026-08-03
Changed
- Catalog cache to DB — zero upstream API hit on request; serves cached catalog instantly
[0.6.2] — 2026-08-03
Changed
- Anime catalog via Kitsu — AniList global outage fallback
[0.6.1] — 2026-08-03
Fixed
- Movie gid empty —
instr()returned 0 for ids without colon, hiding movies from catalog
[0.6.0] — 2026-08-03
Added
- Catalog rework — Trending, Last Play, Anime by day (replaces stats-derived Top/Popular from 0.5.0)
- Web catalog reads the cached catalog data (filled by the 0.6.3 DB layer)
- AGENTS.md — project guide for AI agents + semver versioning rules
[0.5.0] — 2026-08-02
Added
- SQLite persistence —
node:sqlite(zero deps) for stats, per-file access, geo cache and the error log. Replacesdata/*.jsonfiles. - Airport wayfinding UI redesign —
wayfind.jsdesign system (Hanken Grotesk + IBM Plex Mono, yellow signage, 3px black enamel frame).DESIGN.mdspec. - World map (
/map) — ECharts geo map tracking accessor countries via IP geolocation (ip-api.com, cached inip_cache). Counter cards: Countries, Unique IPs, Total users, Active 24h. Hits/Users column. - Error log —
error_logtable,/errorspage server-side sortable + pager, Errors counter card (clickable →/errors) - Country ban —
BANNED_COUNTRIESmiddleware (currently IL) - Stremio catalog from stats — Top, Trending, Anime, Popular surfaced from real usage
- Cast photos —
enrichCastPhotospulls TMDB photos fromcatalog.nuvio.tvmetaapp_extras.cast - v3 in every search tier — OpenSubtitles v3 addon now primary at all tiers (no key, no rate limit)
/statsUpdated column sortable by timestamp
Fixed
resolveReleaseNamein cache-first path + mobile release layout- Russia map glitch + ECharts country-name aliases
- Duplicate geo component removed (map was doubled)
- Country flags use country code (CFLAGS) not language code
Changed
- Bumped version
0.4.0→0.5.0
[0.4.0] — 2026-07-31
Added
- Usage stats — requests, translates, cache hits/misses, errors, per-file access
- Target language dropdown — 75 languages with native labels, default Indonesia
- Test buttons —
/configurepage card layout (OpenSubtitles, OpenAI API, Translation); Test endpoints validate credentials inline - Test endpoints —
/api/test/opensubtitles(search),/api/test/openai(chat completion) - Placeholder subtitle feedback — cache miss serves
[Translating...]cues instead of blank/English;Cache-Control: no-cacheso next play pulls the cached translation - README — full documentation: features, quick start, Docker, config table, how-it-works diagram, API reference, privacy
- OpenAI curl test script —
test-openai.sh [base] [key] [model] npm startscript,mainentry, description, keywords in package.json
Changed
- Bumped version
0.3.0→0.4.0 - Manifest
config.options.targetLangnowstring[](was[value,label][]) — fixes Stremio manifest validation (75 errors) - Landing form: card-based layout instead of flat field list
.gitignore: addeddata/docker-compose.yml: mounted./datavolume
Removed
- jQuery / Select2 / Alpine experiments (reverted to native
<select>) - VTT progressive streaming (Stremio player buffers full response — streaming had no effect)
- Silent
.catch()on cache writes (now logs errors)
[0.3.0]
Added
- Catalog browsing (IMDb via Cinemeta, anime via AniList)
- Synopsis translation
- Subtitle preview + preload endpoints
- In-flight translation dedup
- Tiered subtitle search (target lang > English > any)
- SRT + VTT serving
- Docker support (
Dockerfile,docker-compose.yml)
[0.1.0]
Added
- Initial Stremio addon: OpenSubtitles fetch → LLM batch translate → serve
.srt