A batch of VitePress site updates around the v3.5.0 release:
- Refresh the zh / en / jp homepages into a 3x3 feature grid aligned
with README v3.5.0: keep the foundational "no packet capture /
batch download / mobile playback" cards, add v3.5.0 pillars
(browser extension / broad video source coverage / OpenClaw Skill /
HTTP API / built-in format conversion / one-line Docker). Rewrite
the hero with a tighter "cross-platform video downloader + built-in
sniffing" tagline and point the English hero at the
English-localized `home_en.png`.
- Fix en / jp hero action links that previously routed to the Chinese
root pages (`/guides` → `/en/guides`, `/jp/guides` etc.).
- Add the macOS usage block to the en / jp quick-start guides
(Intel vs Apple Silicon + `sudo xattr -dr com.apple.quarantine`
unblock command), and drop the QQ feedback group tip from those
locales — QQ is China-only, not useful for en / jp audiences.
- Add a new Download API reference page (`/api`) for all three locales,
registered in the sidebar under "开始 / Quick start / 始める".
Covers Base URL (desktop `39719`, Docker `9900`), auth (API key from
Settings page), curl quick-start, download SSE events, and the full
`/api/downloads/*` endpoint reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The English README was reusing the Chinese-localized screenshots
(`home.png`, `home-dark.png`, `settings.png`, `browser.png`), which
left English readers staring at a UI they could not read. Ship a
parallel set of English screenshots under the `_en` suffix and point
`README.md` at them; `README.zh.md` / `README.jp.md` keep their own
assets untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses #638, where a prospective Italian translator asked for clearer
translator onboarding and a way to preview translations in place. The new
guide documents where strings live, the four-file registration path for a
new language, and the Vite HMR dev workflow that already solves the live
preview need. Linked from all three READMEs alongside CONTRIBUTING.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `installer/installer.nsh`: customHeader macro sets Caption to
"Setup - ${PRODUCT_NAME} ${VERSION}" so users can see which release
they're installing from the window title (the default $(^SetupCaption)
omits the version, and re-setting Name trips NSIS warning 6029 which
electron-builder's -WX flag treats as a hard error).
- `scripts/build.ts`: afterAllArtifactBuild hook runs the app-builder
rcedit helper on the generated NSIS installer to rewrite its
FileDescription to "${APP_NAME} installer". electron-builder's
NsisTarget.computeVersionKey() hardcodes VIAddVersionKey /LANG=1033
"FileDescription" "${appInfo.description}", binding the installer's
FileDescription to the app binary's (both drawn from package.json
description); any in-NSIS override collides on the same LANG+key
with a hard "already defined!" error that -WX does not gate.
Post-processing with rcedit sidesteps this and lets installer and
app binary carry distinct descriptions — same approach VS Code's
Inno Setup pipeline uses (where "{AppName} Setup" is the default).
Closes#637
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapses the line-continuation form (`docker run ... \\n -v ... \\n caorushizi/mediago:3.5.0`)
into one line across README.md / README.jp.md so all three language
READMEs match (zh was already single-line). Makes the command easier
to copy-paste — readers don't have to deal with trailing backslashes
that some terminals / browsers mangle when highlighting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI workflow mirrors every built image to Docker Hub
(`caorushizi/mediago`), but the install lists in README (zh/en/jp)
and docs (guides / changelog for all three languages) only mentioned
GHCR. Most users default to Docker Hub, so they would miss the
simpler pull path.
Switch every Docker row into a two-line form showing both registries
explicitly:
- [**Docker Hub**](https://hub.docker.com/r/caorushizi/mediago): docker run ... caorushizi/mediago:3.5.0
- **GHCR**: docker run ... ghcr.io/caorushizi/mediago:3.5.0
Docker Hub appears first (shorter image name, more familiar to most
users) and its label is a link to the Hub page. GHCR is kept as a
second bullet for users who prefer it or are already authenticated.
The README feature-section (`🐳 Docker deployment`) code block was
also switched to the unqualified `caorushizi/mediago:3.5.0` form so
the showcase command defaults to Docker Hub, with a prose note that
the same image lives on GHCR too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Docker Hub mirror workflow used `secrets.DOCKERHUB_USERNAME != ''`
directly inside step `if:` conditions and step-level `env:`
expressions. GitHub's expression parser now rejects that with
`Unrecognized named-value: 'secrets'` — the `secrets` context is only
allowed in a narrow set of positions (mostly `with:` inputs and step
`env:` values, not inside `if:` expressions).
Route the check through a dedicated detection step whose `env:` reads
the secrets (the one permitted way to surface them) and writes a
plain boolean to step outputs. Every downstream conditional then
guards on `steps.dockerhub.outputs.enabled == 'true'` instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every image published to `ghcr.io/caorushizi/mediago` now also gets
pushed to `docker.io/caorushizi/mediago` under the same tags, so users
can `docker pull caorushizi/mediago:3.5.0` without setting up GHCR
auth.
- New `DOCKERHUB_IMAGE` env var pinning the Docker Hub slug.
- New `Login to Docker Hub` step guarded by
`secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != ''`
so forks without Docker Hub credentials can still build + push to
GHCR without failing the login.
- New `Resolve image targets` step that composes the metadata-action
`images:` list at runtime — only adds the Docker Hub target when
both secrets are present. Without this, metadata-action would keep
emitting Docker Hub tags that build-push-action would then 401 on.
- Build Summary shows both `docker pull` hints when Docker Hub is
enabled.
Setup (one-time, in repo Settings → Secrets and variables → Actions):
- `DOCKERHUB_USERNAME`: Docker Hub account name
- `DOCKERHUB_TOKEN`: PAT from https://hub.docker.com/settings/security
with Read+Write scope on caorushizi/mediago
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`docs/guides.md` still linked to v3.5.0-beta.0 assets under a stale
v3.0.1 heading; the English and Japanese versions lagged further
behind at v3.0.0. Update the "Download and install" block on each
guide to point at v3.5.0 (installer / portable / macOS arm64 + x64 /
Linux / Docker) with a link back to the GitHub Releases page for
older builds.
Prepend a v3.5.0 entry to each `changelog.md` using the same
user-facing highlights the README now uses (browser extension /
YouTube + 1000+ sites / OpenClaw Skill / HTTP API / in-app format
conversion / Docker simplification / Go backend). Older entries
(v3.0.0 and earlier) are left untouched — changelog is append-only.
`docs/history.md` intentionally left alone: it's the "old permalinks"
archive and doesn't track the current release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps `docker build -t mediago:local .` so contributors don't have to
remember the tag convention, and keeps local Docker builds discoverable
alongside pack:electron / release:electron in `pnpm run`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`scripts/download-deps.ts` treats the aria2 entry as `source: "local"`
and copies from `extra/aria2/<os>/<arch>/aria2c` into `.deps/`. Every
other downloader (ffmpeg / N_m3u8DL-RE / BBDown / yt-dlp / mediago) is
`source: "github"` and fetched over the network, so they reach the
image fine — but the Docker build context never included `extra/`,
which made `copyLocalTool()` hit its "source missing" branch and
silently skip aria2. Resulting image had no `aria2c`, any direct
download inside the container failed with a binary-not-found error
the moment a task was dispatched.
Add a `COPY extra/ extra/` in the node-builder stage, right before
`pnpm deps:download` runs, so the vendored binary is actually visible
to the script.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a released image has to be re-cut (e.g. the broken 3.5.0 that
shipped with the stale per-downloader flags), there was no clean way
to re-publish under the same tag from the workflow — only a fresh
git tag push would trigger a build.
Extend `workflow_dispatch` with two inputs:
- `tag` — image tag to publish under (e.g. `3.5.0` to overwrite
the existing one, or `3.5.0-fix.1` for a side-by-side).
Empty value falls back to the pre-existing
`dev-<run_id>` behaviour.
- `push_latest` — when rebuilding a stable release manually, optionally
re-stamp the `latest` tag at the same time.
`docker/metadata-action` already emits the standard OCI labels,
including `org.opencontainers.image.revision` sourced from `github.sha`.
That field alone is enough to tell two same-tag builds apart via
`docker inspect` — no Dockerfile changes needed, no manually-computed
build args. Summary step now reads version / commit from metadata-action's
own outputs (`steps.meta.outputs.version`) and `github.sha`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The image was failing at startup with:
flag provided but not defined: -m3u8-bin
mediago-core consolidated the per-downloader `--*-bin` flags into a
single `--deps-dir` in 3.5.0-beta.1, but the Dockerfile was still
passing the old five flags (`--m3u8-bin`, `--bilibili-bin`,
`--direct-bin`, `--mediago-bin`, `--ffmpeg-bin`). Go flag parsing
rejects unknown flags, so the container exited immediately.
While fixing, extracted the invocation into
`scripts/docker-entrypoint.sh` rather than leaving a nine-line
quoted-array `CMD` at the bottom of the Dockerfile:
- `exec mediago-core …` replaces the shell so SIGTERM from
`docker stop` reaches the Go process directly — no shell parent to
swallow signals.
- `"$@"` forwards any extra flags from `docker run image … --foo=bar`
so callers can override individual options without replacing the
whole command.
- Switching from `CMD` to `ENTRYPOINT` makes the script the fixed
launch path; runtime overrides still work via the forwarded `"$@"`.
- Editing default args no longer touches the Dockerfile (and thus
doesn't invalidate the full image layer).
New `.gitattributes` pins `*.sh` to LF line endings so a Windows
checkout doesn't turn the entrypoint into a `/bin/sh^M: bad interpreter`
error inside the Linux container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three READMEs had drifted into a dev-first shape — the first screen
of each started with pnpm / install commands before telling end users
what the product actually does — and the Japanese version had fallen
behind (outdated commands, no v3.5.x downloads, screenshots pointing
at an external CDN rather than the in-repo images).
Rework all three around the v3.5.0 user-friendly feature story and
move the developer setup into its own file:
- Unified skeleton across zh/en/jp: header badges → one-sentence pitch →
six feature sections (browser extension · YouTube / 1000+ sites ·
OpenClaw Skill · HTTP API · in-app format conversion · Docker +
LAN) → screenshots → v3.5.0 downloads (+ BT Panel one-click) →
v3.5.0 highlights → tech-stack badge row → acknowledgements →
disclaimer → link to CONTRIBUTING.
- Header elements preserved verbatim: nav links, language switcher,
MediaGo Pro / Try Now banners, downloads / stars / forks / GitCode /
Trendshift badges, 4 screenshots, BT Panel one-click deployment,
the full 5-point disclaimer.
- Tech-stack prose list compressed to a single row of shields.io
badges (React / Electron / Vite / TypeScript / Tailwind / shadcn/ui /
Go / Ant Design).
- Acknowledgements gain aria2 (now powering direct downloads after the
gopeed swap).
- README.jp.md specifically: fixed four `static.ziying.site/images/*`
references to use the in-repo `./images/*` (matching zh/en); replaced
the outdated v3.0.0-only download block with v3.5.0; added the
English-language switcher link that was missing from the header.
- README.md/zh.md: dropped the v3.0.0 changelog block (history lives
on GitHub Releases) and the "Quick Start" / "Running locally" pnpm
command blocks.
New file: `CONTRIBUTING.md`. English only — prerequisites, repo layout,
a short list of the commands contributors actually type (deps:download
/ dev:electron / dev:server / pack:electron / release:electron / check),
Conventional Commits note, PR guidance. Each README ends with a one-
line pointer to it in the local language.
No product behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users naturally expect the "Current version" label in chrome://extensions
to match the version shown inside the MediaGo window. Tie both to a single
source of truth: `apps/electron/app/package.json`, which Electron's build
pipeline (`apps/electron/tsdown.config.ts`) already reads and exposes as
`process.env.APP_VERSION`.
`manifest.config.ts` now imports the same file and:
- `version` — stripped down to the numeric prefix (`split(/[-+]/)[0]`)
so Chrome accepts it. Chrome's manifest parser only
allows 1–4 dot-separated integers; SemVer pre-release
or build suffixes (`-beta.2`, `+build.7`) get rejected
at install time.
- `version_name` — keeps the full SemVer string. It's free-form, not
used for update-ordering, and is what chrome://extensions
displays when present.
Effect: bumping `apps/electron/app/package.json` once is enough to
re-stamp both the Desktop binary and the bundled extension manifest.
The extension's own `packages/mediago-extension/package.json` is a
private workspace package — its `version` field is unused by the
manifest and intentionally left alone to avoid a pointless sync step
on every release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Twitter-style page titles like "(2) 主页 / X" land a literal "/" in
the download filename. Gopeed used to strip such characters silently;
aria2 is strict and passes them through to the OS, which then reads
"/" as a path separator — the save ends up at
`.../(2) 主页 /X-....mp4` pointing at a non-existent sub-directory
and fails with `ERROR_PATH_NOT_FOUND (errNum=3)`.
Sanitize once, at the task-creation boundary, so the DB row, the
downloader command-line `-o` arg, and the post-download
`CheckFileExists(rec.Name, ...)` probe all agree on the same
filesystem-safe value.
- New exported `core.SanitizeFilename` replaces reserved path /
wildcard characters (`\ / : * ? " < > |`) and ASCII control chars
with `_`, and right-trims dots / spaces (Windows strips those
silently, producing a filename that doesn't match the DB row).
Falls back to "download" if every character was illegal.
- `service/download_task.go` `AddDownloadTask` and `AddDownloadTasks`
run titles through `SanitizeFilename` before the
`FindByName` de-duplication check, so both the dedup lookup and
the persisted row see the cleaned value.
- `core/downloader.go` `buildArgs` still calls `SanitizeFilename`
defensively on `p.Name` — cheap, and guards any future path that
bypasses the service layer.
Applies to every downloader (aria2, yt-dlp, BBDown, N_m3u8DL-RE,
mediago) since the fix is in the shared `name` arg-building branch
used by all Schema entries. Pre-existing broken tasks in the DB will
still fail "file not found" on the UI and need to be deleted +
re-created.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
If a stray MediaGo instance (typically the installed Desktop build
running in the background from the Start menu) already holds the
preferred port, a freshly-spawned child fails to bind and exits
immediately — but `waitForHealthy()` happily answers green because
the *other* instance's `/healthy` responds on the same port.
ServiceRunner then considers the service "started" and every
subsequent request silently lands on the wrong process. In practice
this surfaced as dev mode talking to the installed Go Core and
reporting a stale gopeed binary path, even though the dev build had
already been rebuilt with aria2c.
Pre-check: before spawning, open and close a throwaway TCP listener
on `<bindHost>:<preferredPort>`. If the bind fails, throw a
human-readable error instead of proceeding. The tiny check-vs-bind
race window is acceptable for catching the real-world "long-running
stray instance" case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gopeed's last release is aging and its SChannel-linked Windows build
chokes on modern CDN TLS handshakes (we hit SEC_I_MESSAGE_FRAGMENT on
twimg.com). Replace it with aria2 — same short flags (-x/-s/-k carry
over verbatim), stricter arg handling, and well-trodden cross-platform
builds.
Binaries are vendored in-tree at extra/aria2/<os>/<arch>/ rather than
pulled from a single GitHub release, because aria2 static builds for
Linux / macOS / Windows come from different upstream repos. To keep
the deps pipeline unified:
- `scripts/download-deps.ts` grows a "source": "local" branch that
copies from `extra/<tool>/<os>/<arch>/` into `.deps/<os>-<arch>/`
instead of fetching from GitHub. Resulting layout matches the rest
of the tools, so binaryResolver.ts needs no changes.
- `scripts/deps-versions.json`: `gopeed` entry removed, `aria2`
entry added with source:"local" + path:"extra/aria2".
- `apps/core/internal/core/types.go`: BinaryNames[TypeDirect]
`"gopeed"` → `"aria2c"`.
- `apps/core/internal/core/schema/loader.go`: direct schema's Args
map to aria2's flags (-d / -o, plus --console-log-level=notice /
--summary-interval=1 / --allow-overwrite=true /
--auto-file-renaming=false for parseable output and predictable
rerun behaviour). ConsoleReg regexes updated to aria2's summary
line format (e.g. "DL:512KiB" for speed, "(83%)" for percent).
`--check-certificate=false` tacked on as a workaround for the
SChannel handshake issue on the bundled 1.19.0 Windows build —
proper fix is upgrading the vendored binary to a build that links
against OpenSSL (e.g. 1.37.0), comment calls that out.
- `apps/core/.env`, `Dockerfile`, `apps/electron/scripts/build.ts`,
`apps/core/README.md`: gopeed → aria2c / aria2 references.
No DB migration: existing `type: "direct"` rows keep working, the
underlying binary just swaps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ServiceRunner used to pass the detected LAN IPv4 as the `HOST` env var
when `internal: false`, so the Go Core bound *only* to that LAN IP —
requests to 127.0.0.1 / localhost were refused. That blocked the
browser-extension's `desktop-http` mode (fixed at `http://127.0.0.1`).
Split bind host from display host:
- Bind (`HOST` env to child): `0.0.0.0` when `internal: false`, else
`127.0.0.1`. Listener now accepts traffic on every interface.
- Display (`state.host` / `getURL()`): keeps the detected LAN IPv4
when `internal: false` so the settings UI can surface a shareable
LAN URL, falls back to 127.0.0.1 otherwise.
Net effect: Desktop is reachable via 127.0.0.1, localhost, and its LAN
IP simultaneously, while the UI still shows the LAN address.
Also translates all remaining Chinese JSDoc / inline comments in
`index.ts` and `utils.ts` to English, matching the rest of the package.
Behaviour otherwise unchanged.
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>
Mirror the English-only convention already used in the Electron/UI
scripts. Covers dev.ts (start dev server / build Player UI / compile
dev binary) and release.ts (build all-platform binaries / package /
clean). Behaviour unchanged — message text only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MediaGo extension shipped Chinese-only (50+ hardcoded strings across
popup, options, background). Wire up i18next + react-i18next so the UI
follows the browser language by default and lets the user override to
zh / en in a new LanguageCard on the options page.
- `ExtensionSettings.language: "system" | "zh" | "en"` (default system),
persisted via existing `chrome.storage.local`
- `resolveLanguage()` maps system -> `chrome.i18n.getUILanguage()` or
navigator.language with a zh/en fallback
- Bootstrap one i18next instance per page (popup / options), subscribe
to `chrome.storage.onChanged` so switching on one surface updates the
other without a reload
- Background service worker emits `LocalizedMessage` descriptors
(`{ key, values? }`) for toast copy; popup calls `t(key, values)` so
the SW never has to carry an i18n instance
- Full zh/en coverage for popup, options, error paths
- Chrome-native `_locales/{en,zh_CN}/messages.json` + `__MSG_*__` for
`manifest.name` / `description` / `action.default_title` so the
chrome://extensions row and toolbar tooltip follow the browser UI
language (independent dimension from the in-UI toggle, by design)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The useUrlInvoke hook at apps/ui/src/hooks/use-url-invoke.ts reads
both `silent` and `downloadNow` query params; they were previously
hard-coded on the extension side. Surface them as user toggles so
Schema-mode users can opt into "open the download form for review"
and both modes can opt into "start downloading immediately instead
of just queuing".
- ExtensionSettings gains `downloadNow` (default false) and
`schemaSilent` (default true).
- importViaHttp threads `startDownload` into the POST body.
- buildTaskDeeplink writes `silent=1` / `downloadNow=1` into the
deeplink URL based on the flags (falsy => omit the key, matching
the truthy-check in useUrlInvoke).
- New headless Switch component (no @radix-ui/react-switch dep).
- New ImportBehaviourCard in options, with a patch-style save hook
so each toggle persists immediately.
- use-options.ts now merges on top of the current persisted settings
so the server-config card doesn't wipe the new fields.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A Manifest V3 extension that sniffs downloadable video / audio URLs
across every site the user visits and hands captured sources to a
MediaGo server in one click. Ships unlisted (load-unpacked .zip), no
Chrome Web Store.
Dispatch modes (user-picked in options, no silent fallback):
- desktop-schema: navigates the current tab to
`mediago-community://index.html/?n=1&silent=1&url=…` using the
cat-catch `chrome.tabs.update` pattern. Reuses the existing
`useUrlInvoke` hook in apps/ui — no new deeplink plumbing.
- desktop-http: POST http://127.0.0.1:9900/api/downloads against the
Go Core bundled in a running Desktop.
- docker-http: same POST against a user-configured host, with an
optional X-API-Key header for `--enable-auth` deployments.
UI is React 19 + Tailwind v4 + shadcn/ui (new-york, neutral), matched
to apps/ui's stack. Popup shows per-tab sources with a red badge count;
options page has a 3-mode radio + per-mode field panel.
Supporting changes:
- Abstract sniff filter rules into @mediago/shared-common/sniff and
point the Electron sniffing helper at the same exports so desktop
and extension stay in lock-step.
- Tighten the YouTube host rule to actual video / short / live / embed
URLs (drops the homepage and feed false-positives).
- Fix the macOS electron-builder config: CFBundleURLSchemes was
hard-coded to a test string "mediagoaaa" — now sourced from
process.env.APP_NAME (mediago-community in .env) like everywhere
else. Also add a top-level `protocols` entry for clarity.
- Vite build pulls APP_NAME from the repo root .env via loadEnv() +
`define`, so the scheme name stays single-sourced.
- Root scripts: `pnpm dev:extension`, `pnpm build:extension`,
`pnpm pack:extension` (+ scripts/pack-extension.ts that cross-
platform-zips dist/ into release/).
Co-Authored-By: Claude Opus 4.6 (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>
The tray context menu was built once at startup, before the language
was loaded from Go Core, so its labels were stuck in the fallback
locale and never updated when the user switched languages. Keep the
Tray instance on the class, extract the menu build into refreshTrayMenu,
and subscribe to i18n "languageChanged" so the menu follows the
current language for both startup hydration and runtime changes.
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>
- Upgrade zustand from 5.0.0-rc.2 to ^5.0.0 (resolved 5.0.12)
- Fix player:dev and player:build scripts that referenced non-existent
@mediago/player-build, now correctly point to @mediago/core-build
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Correct 8 inaccuracies where CLAUDE.md diverged from the actual codebase:
server is a Node.js launcher not Koa 3, apps/player/ doesn't exist,
remove non-existent commands, fix adapter layer and event bridge file
names, correct DI and module format claims.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>