Go Core already broadcast a `download-create` SSE event whenever
`/api/downloads` POST lands (UI, browser extension, Docker clients —
any route). The UI listened, but only to bump the sidebar badge via
`useDownloadStore.increase()`. The SWR list fetched by `useTasks`
stayed stale until the user refreshed manually, so tasks imported
through the extension's desktop-http / docker-http modes never
appeared without a page reload.
Fan the same SSE event through the existing `dispatchDownload`
pipeline so `useTasks` can mutate its SWR cache alongside the other
lifecycle events (success / failed / stopped).
- `packages/shared/common/src/types/index.ts`: new
`DownloadCreatedEvent` (`type: "created"`, carries `{ ids, count }`).
- `apps/ui/src/api/events.ts`: existing `download-create` handler now
additionally calls `dispatchDownload({ type: "created", ... })`
after its badge-increment side effect. Parses `ids` from the
payload for downstream filtering if anyone needs it later.
- `apps/ui/src/hooks/use-tasks.ts`: adds `isCreatedEvent` type guard
and calls `mutate()` on hit, same pattern as the other events.
Internal task creation (UI form submit) is untouched — it still
refreshes via its own local mutate. The SSE path only matters for
tasks that show up out-of-band.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two separate bugs had to be fixed together before copy worked:
1. `terminal-dialog.tsx` used to swallow every contextmenu event
inside the dialog (`e.preventDefault(); e.stopPropagation()`) —
killing Electron's built-in "Copy" menu for selected text in the
xterm log. Drop the preventDefault so the native menu fires, but
keep stopPropagation (now scoped to the contextmenu handler
alone) so the event doesn't bubble up through the React tree to
`DownloadTaskItem`'s `onContextMenu` and pop its "select /
download / refresh / delete" menu instead. Radix Dialog portals
the DOM to document.body, but React synthetic events still
travel the virtual tree — this is the classic portal-bubbling
gotcha.
2. xterm.js does not bind Ctrl+C / Cmd+C to "copy selection" by
default; it forwards them as control chars. Since the log view
uses `disableStdin: true`, hijacking those keys is safe.
`attachCustomKeyEventHandler` now intercepts copy shortcuts,
writes the selection to the clipboard via `navigator.clipboard`,
and returns false so xterm stops handling the event. No selection
→ passthrough.
Result: both right-click → Copy and Ctrl/Cmd+C paths work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ship the MediaGo browser extension alongside the Desktop installer so
users can "Load unpacked" it from Chrome / Edge without building the
repo or downloading a separate zip. Adds a discovery path from inside
the app, a three-language doc page, and fixes the Desktop HTTP port
the extension was pointing at.
Packaging:
- `pnpm build:electron` now also builds `@mediago/extension` (turbo
filter), and `apps/electron/scripts/build.ts` copies the dist to
`app/build/extension/` and declares it in electron-builder
`extraResources` → installers land `resources/extension/`.
Runtime:
- `resolveExtensionDir()` in binaryResolver — dev points at the
monorepo dist, prod at `process.resourcesPath/extension`, env
override via `MEDIAGO_EXTENSION_DIR`.
- New IPC `app.getExtensionDir()` returns the resolved path. UI pairs
it with the existing `shell.open()` (no dedicated open-folder IPC)
so the pattern matches configDir / binDir / localDir.
Settings UI:
- New "Browser extension directory" button in Settings → More Settings,
right next to the existing folder shortcuts. Web/server mode hides
the row behind `isWeb` and the stub returns "".
- New i18n key `extensionDir` in shared zh/en resources; duplicate
`currentVersion` key removed from zh.ts along the way.
Port fix:
- Extension `DESKTOP_HTTP_BASE` corrected from `:9900` → `:39719` to
match the Electron-side `preferredPort`. 9900 is the standalone
Go Core / web-server port — two separate deployments.
Docs:
- New `docs/extension.md` + `docs/en/extension.md` + `docs/jp/extension.md`
covering what the extension does, how to install the unpacked
build, the three dispatch modes (schema / desktop-http / docker-http),
import-behaviour toggles, language switching, and common pitfalls.
- Sidebar entries registered in `.vitepress/config.ts` for all three
locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DevTools profiling showed a ~319ms long task when switching to the
settings page, dominated by scripting inside node_modules — Ant Design
Form.Item registration and cssinjs style generation firing for ~25
fields in a single synchronous pass. Chunk loading was only ~9ms, so
earlier fixes around lazy-import and Suspense fallbacks missed the real
cause.
Changes:
- Stream the 6 cards in one per animation frame (visibleCount driven
by requestAnimationFrame), so the longest task is a single card's
registration instead of the full page. The first card paints
immediately and the rest follow over the next few frames.
- Skip the initial setFieldsValue via an isFirstSync ref;
initialValues already seeds the form, so that effect was only
forcing Ant Form to diff every Form.Item again right after mount.
- Wrap cardSections in useMemo with a narrow dep list
(settings.apiKey, settings.local, envPath, updateAvailable, stable
callbacks) so SSE config-changed updates don't rebuild the 340-line
JSX tree unless those specific fields changed.
- Move the key from <Card> to the outer <div> so React stops treating
each re-render as a remount of the card subtree.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make favorite title optional in both the DTO (drop binding:"required")
and the add-favorite modal (no validator). Server falls back to the
URL when the title is empty so the list entry always has a label.
- Auto-fill the title via the existing GET /api/url/title scraper when
the user leaves the URL field, unless they already typed their own
title. Failures are swallowed (server fallback still applies).
- Reorder the modal so URL is on top and Title below, matching the
actual data-flow: users paste a URL first and the title follows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Go Core now broadcasts a "download-create" SSE event after
POST /api/downloads succeeds, carrying the new task ids and count.
The renderer's events.ts listens for it and calls useDownloadStore's
increase() imperatively, so the sidebar badge updates regardless of
which WebContents issued the request — the source-extract overlay
dialog has its own Zustand instance and can no longer silently swallow
the increment.
- Remove the now-redundant local increase() calls in download-form and
browser-view-panel so the counter is incremented exactly once per task
from a single source of truth.
- Favorite.Create returns 409 Conflict with a translated
MsgURLAlreadyExists message when the URL already exists, instead of
500 with the raw "url_already_exists" string.
- http.ts response interceptor now surfaces the server's translated
message on 4xx/5xx instead of Axios's generic
"Request failed with status code XXX".
- Simplify getFavIcon: drop the 1s <img> probe that dropped most URLs
and return the canonical /favicon.ico; <Avatar> already falls back to
a link icon when the image fails to load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Resolve AppStore.language at apply-time via a shared resolveAppLanguage
helper so both renderer (navigator.language) and Electron main
(app.getLocale) follow the actual OS locale instead of silently
falling back to zh when the stored value is "system".
- Settings page: horizontal form with a fixed label column, borderless
cards flowing naturally in a 2-column CSS columns layout; drop
width="xl" on inputs and wrap button groups to fix English-label
overflow.
- scripts/download-deps: add a PowerShell fallback for zip extraction
on Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rewrite README.md in idiomatic English as the default language
- Move original Chinese README to README.zh.md, delete README.en.md
- Polish ~80 UI translation strings in en.ts for natural English
- Fix hardcoded Chinese in Skills setup commands (setting-page)
- Fix mismatched translation key: reppeatPassword → repeatPassword
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add youtube download type with yt-dlp schema (CLI args, progress regex)
- Replace 6 individual --xxx-bin flags with single --deps-dir flag
- Add BinaryNames map in Go core to auto-resolve binary paths from deps dir
- Simplify Electron/Server binary resolvers to return depsDir only
- Add yt-dlp to deps-versions.json (v2026.03.17)
- Add YouTube option to UI download form with URL auto-detection
- Fix download-terminal.tsx writing [object Object] instead of log content
- Improve error messages when binary not found (include path and type)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract shared navigation logic into useBrowserActions hook, fix race conditions
in sniffing helper, add event listener cleanup in webview service, split store
selectors for granular re-renders, memoize list items, throttle ResizeObserver,
and add loading/error states to favorites list. Also remove unused getMachineId
IPC handler and fix IPC callback type signatures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of capturing a screenshot, transferring a large DataURL via IPC,
and hiding the browser WebContentsView, use a separate overlay
WebContentsView layered above the browser view to render the download
form. This eliminates flicker, avoids potential page state loss, and
removes the inefficient screenshot transfer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use correct GoEnvPath fields (configDir/binDir) instead of non-existent workspace/binPath
- Defer form.setFieldsValue after modal opens so Form is mounted
- Add Input child to hidden Form.Item with name prop to satisfy antd 6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add mediago-core (caorushizi/mediago-core) as a fourth download type
alongside m3u8, bilibili, and direct. This enables HLS/DASH streaming
downloads using the custom Go-based downloader.
Changes span the full integration path: dependency download config,
Go Core backend (type, schema, CLI flag, binary map), binary resolution
for Electron/Server, shared TypeScript types, i18n labels, UI dropdown,
dev scripts, and Dockerfile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skills:
- Rewrite SKILL.md as OpenClaw Skill (remove scripts dependency)
- Guide users to install MediaGo + initialize config in one message
- Support Chinese and English natural language commands
- Remove mediago-api.sh (use curl directly, cross-platform)
Docs:
- Add OpenClaw Skill page (zh/en/jp) with install, config, usage guide
- Add to VitePress sidebar for all 3 languages
Settings UI:
- Add "Skills 设置" tab with install command + init command
- Electron mode: init shows URL only
- Docker mode: init shows URL + API key
- One-click copy buttons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The auth status API returns { setuped: bool } but the frontend was
checking for non-existent fields (initialized, enableAuth). Fix to
use the correct field name so unauthenticated users are properly
redirected to the signin page in server mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor video player API from filesystem scanning to database-backed:
- Video list queries downloads with status="success" + file existence check
- Add GET /api/v1/videos/:id endpoint for playing specific video by task ID
- Add playerUrl to /api/env response (computed from request host)
- Whitelist player/video paths in auth middleware
- Fix video URL generation to preserve folder structure (relative path)
- Fix player-ui Vite base path and video URL resolution under /player/
- Add openUrl implementation to web platform stubs
- Player-UI supports ?id= query param to auto-play specific video
- Play button now passes task.id instead of filename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. events.ts: progress polling was using /api/downloads/active which
returns DB records without percent/speed fields. Changed to /api/tasks
which returns TaskInfo with real-time progress data.
2. download-item.tsx: lint fix had renamed callback params from `task`
to `taskItem` but missed updating all references in the function body,
causing ReferenceError. Reverted param names back to `task`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the frontend hashed passwords with MD5 + a hardcoded salt
before sending to the backend. This exposed the salt in client-side JS
and used an insecure hash algorithm.
Backend changes:
- Setup: accepts plain password, bcrypt hashes it, generates UUID apiKey
- Signin: accepts plain password, bcrypt verifies, returns stored apiKey
- Status: checks passwordHash instead of apiKey
- AppStore: new passwordHash field
- Middleware: add X-API-Key header support (was only reading Authorization)
- CORS: allow X-API-Key header
Frontend changes:
- Signin page: send plain password, receive apiKey from response
- Remove md5/crypto-js dependency and APIKEY_SALT_KEY constant
- api/download-task: auto-inject localPath/deleteSegments from store
(was lost during go-adapter migration, caused EOF errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of manually calling setHttpApiKey() after signin, use an axios
request interceptor that reads apiKey from useAppStore.getState() on
every request. This ensures the header is always in sync with the
stored apiKey, regardless of when or how it was set.
Removed: setHttpApiKey() function (no longer needed).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After login, the apiKey was saved to Zustand/localStorage but never
set on the http axios instance header, causing subsequent API calls
to return 401.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proper TypeScript return types to every api/ function, using
existing shared types (Favorite, Video, Conversion, AppStore, etc.)
from @mediago/shared-common.
New types added:
- GoEnvPath (api/config.ts) for /api/env response
- AuthStatus (api/auth.ts) for /api/auth/status response
Updated SWR hooks to use typed data instead of Record<string, unknown>
casts: useFavorites, useConfig, useEnvPath, useAuthApi, useConversions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues:
1. http.ts 401 handler used window.location.hash (HashRouter syntax)
but the app uses BrowserRouter — changed to window.location.pathname
2. useAuth() blindly checked localStorage apiKey, which is always empty
on first visit. Now queries Go Core auth status first: only redirects
to /signin when auth is enabled and configured (or needs setup)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar: toggle between embedded and external browser window mode,
show extract page in sidebar even when opened externally
- Home page: remove redundant material extraction button
- Converter: temporarily disable video format options, keep audio only
- Electron: remove node-machine-id dependency from package.json
- Tool bar: fix combineToHomePage argument structure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PageContainer was missing flex-col, causing pagination to render inline
with the list. Add flex-col + gap-3 to PageContainer and remove
duplicate padding/bg from the inner list container.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded { current: 1, pageSize: 500 } with dynamic pagination
state. Default pageSize is 50 with an Ant Design Pagination component
at the bottom of the list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move machineId generation from Electron (node-machine-id) to Go Core.
On first startup, if machineId is empty in appStore config, Go Core
generates a UUID and persists it. This makes machineId available in
both Electron and Web modes via the config API.
UI now reads machineId from getConfig() instead of getMachineId() IPC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PlatformApi methods via Electron IPC return { code, data, msg } objects.
After migrating from useAPI() to usePlatform(), the auto-unwrap logic was
lost, causing consumers to receive raw objects instead of extracted data
(e.g. onSelectDownloadDir() returned [object Object] instead of a path).
Fix: wrap all PlatformApi function calls in the Proxy get trap to
auto-unwrap { code, data } responses, matching the old useAPI() behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- core: move defer logger.Sync() after logger re-init to avoid nil panic
on early startup
- electron: replace console.log with ElectronLogger in DownloaderServer
and ElectronApp for consistent log output
- ui: add 1s delay before fetching coreUrl in Electron mode to wait for
Go core to finish starting
- ui: guard data?.list access in converter-page to prevent crash when
data is undefined
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Redesign the "Add File" flow as a modal dialog containing:
- File path field with "Browse" button (opens native file picker)
- Output format selector (Video: MP4/MKV/WebM, Audio: MP3/AAC/FLAC/WAV)
- Quality selector (High/Medium/Low)
- Two action buttons: "Add to List" (saves for later) and
"Convert Now" (saves and starts conversion immediately)
Also fix silent failure when adding files — getFileName() used
new URL() which throws on local file paths. Replaced with simple
path.split() extraction and added try-catch error handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move ffmpeg execution from Electron to Go Core so conversion works in
both Electron and web server modes.
Go Core backend:
- Add -ffmpeg-bin CLI flag to pass ffmpeg binary path
- Enhance Conversion DB model with status, outputPath, outputFormat,
quality, progress, and error fields
- Create converter executor (service/converter.go) that spawns ffmpeg,
builds args per format/quality, and parses stderr for progress
- Add StartConversion/StopConversion to service with SSE events for
real-time progress (conversion-start/progress/success/failed/stop)
- Add POST /api/conversions/:id/start and /:id/stop endpoints
Supported formats:
- Video: MP4 (H.264), MKV (H.264), WebM (VP9) with CRF quality
- Audio: MP3, AAC, FLAC, WAV with bitrate quality presets
- Quality presets: high/medium/low mapping to CRF and bitrate values
Frontend (converter-page):
- Add format selector (Video/Audio groups) and quality dropdown
- Show per-item status badge, progress bar during conversion
- Action buttons: Start/Stop/Open Folder/Delete based on status
- "Convert All" batch button for pending items
- SWR auto-refresh during active conversions
Integration:
- Pass -ffmpeg-bin from Electron and core:dev to Go Core
- Remove old broken Electron-only convertToAudio code
- Add startConversion/stopConversion to core-sdk and GoApi
- Add i18n keys for new UI elements (en/zh)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UI app:
- Add Vite manual chunks (vendor, antd, zustand) for better caching
- Wrap DownloadTaskItem with React.memo() to prevent list re-renders
- Wrap IconButton with React.memo() to prevent re-renders in lists
- Add missing Suspense fallback to SigninPage route
Player app:
- Add Vite manual chunks (videojs, vendor) to isolate video.js bundle
- Remove duplicate resize calculation in usePlayerSize hook
(ResizeObserver already handles container size changes)
- Disable SWR revalidateOnFocus/revalidateOnReconnect for video list
(list only changes when user adds/removes videos)
- Extract PlaylistItem into memoized component to prevent re-renders
when sibling items change, and eliminate duplicated rendering logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the app polled GET /api/tasks every second unconditionally
from startup, even with no downloads running. Now polling is driven
by SSE events: started on download-start, stopped when no tasks have
Downloading status (checked after success/failed/stop events).
Applied to both Electron main process (downloader.server.ts) and
web/UI mode (go-event-bridge.ts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Converted all Chinese-language comments across 47 source files to
English, covering Go backend (apps/core, apps/player), TypeScript
scripts, Electron main process, UI components, and shared packages.
Only comment text was modified — code, string literals, log messages,
and i18n translation values were left untouched.
Also fix pre-existing lint errors in scripts/release.ts and
scripts/utils.ts (node: protocol prefix, unused imports, bare catch).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three root causes fixed:
1. ElectronStore migration deleted Go Core's config.json on every startup.
The old migration code unconditionally ran unlinkSync() on config.json
in the workspace directory — the same file Go Core uses for persistence.
Removed the migration entirely since it has already run for all users
(window-state.json now holds the bounds data).
2. core:dev mode saved config to the wrong directory (log dir instead of
data dir) because -config-dir was not passed. Added config_dir to
devConfig and the dev() command args, pointing to ~/.mediago/data to
avoid collision with the old v2.0 ~/.mediago/config.json format.
3. syncCLIToAppStore() overwrote user settings with CLI default values
on every Go Core startup in core:dev mode. Removed the function; CLI
args now only set initial cfg defaults, while appStore (user config)
takes precedence via the existing syncAppStoreToCfg().
Also aligns blockAds default (true) between Go Core and frontend Zustand store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getGoApi() was called eagerly inside useMemo during component mount,
before initGoAdapter() had run. Now GoApi methods resolve getGoApi()
at invocation time, so useAPI() can safely be called in App.tsx before
the adapter is initialized.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major cleanup of the frontend adapter layer:
1. Type split: MediaGoApi (55 methods) → GoApi (22 data/CRUD methods via
Go Core HTTP) + PlatformApi (~30 Electron-native methods via IPC)
2. Remove Proxy-based routing: replace the fragile apiAdapter Proxy +
GO_METHODS set + ALL_API_METHODS array with direct object composition.
GoApi methods never fall through to Electron IPC.
3. Clean dead code: remove 18 preload methods that called IPC channels
with no handler (getFavorites, getAppStore, createDownloadTasks, etc.)
Remove unused event constants (SOCKET_TEST, SETUP_AUTH, SIGNIN, etc.)
4. New platform-stubs.ts: explicit no-op stubs for web/server mode
instead of a catch-all Proxy that returns empty responses.
5. Simplified useAPI: direct spread of wrapped GoApi + PlatformApi
instead of dynamic Object.keys().reduce() enumeration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add adapterReady state to App.tsx: show Loading until initGoAdapter
completes, preventing API calls before goHandle is set (fixes
"No handler registered" errors for get-favorites, get-app-store, etc.)
- Add error handling and debug logging for player server startup
- Prepend core:build && player:build to dev:electron, dev:server,
pack:electron, and release:electron scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Sync Go Core config to Zustand on app startup so settings persist
across restarts (Go Core is the single source of truth).
2. Go Core Create handler now respects startDownload param — downloads
start immediately when "Download Now" is clicked.
3. Player integration fixes:
- Electron: graceful skip when player binary is not built
- Server: start player service alongside Go Core, expose playerUrl
- Web adapter: derive playerUrl from current host instead of empty string
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump knip to v6.1.1, turbo to v2.9.1
- Remove oxlint-tsgolint devDependency
- Remove hidden={isWeb} from showTerminal setting item
- Format .vscode/settings.json and setting-page/index.tsx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix PTYRunner.readPTYOutput to use raw chunk reads instead of a
broken bufio.Scanner split that called onStdLine with the entire
remaining buffer on every invocation, causing log lines to be
written multiple times
- Remove unused flushInterval field and NewPTYRunnerWithInterval
- Add convertEol: true to XTerm so bare \n in stored logs is treated
as \r\n, preventing misaligned output when replaying log files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete DownloadController (4 methods now handled by Go Core go-adapter),
move showDownloadDialog to WebviewController. Remove exportFavorites,
importFavorites, exportDownloadList from GO_METHODS so Electron mode
uses IPC with native file dialogs instead of bypassing them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update go-adapter getEnvPath to call the new /api/env endpoint instead
of returning hardcoded empty strings for binPath and workspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add notification badge update after creating download tasks
- Add defensive null check in useAPI to prevent destructuring undefined adapter results
- Import download store for increase() calls in download-form
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove old Koa/Socket.IO server, replace with minimal ServiceRunner launcher
- UI now connects directly to Go Core via SDK (HTTP + SSE), no middleware
- Add GoEventBridge for SSE events and progress polling
- Add Dockerfile and docker-compose for single-container deployment
- Fix web mode adapter Proxy ownKeys for proper method enumeration
- Fix auth flow: setupAuth now sets apiKey, App.tsx passes stored apiKey
- Add error handling in download-form for getVideoFolders
- Remove conversion.controller import (file was deleted)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>