Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,20 @@ export function DesktopUpdate() {
runtime_pairing_required: zh ? "旧版启动器未能自动选择可用组件。请检查 App 更新,或通过恢复入口重新连接。" : "The older launcher could not select a usable runtime. Check for an App update or reconnect through recovery.",
idle: zh ? "App 启动时会检查并安装当前通道的可用更新;也可在此手动检查。" : "On launch, the App checks and installs available updates on its current channel. You can also check here.",
runtime_required: zh ? "请完成匹配组件安装,或检查 App 更新。" : "Install matching components or check for an App update.",
connecting: zh ? "正在连接更新后的服务…" : "Connecting to updated services…",
connecting: zh ? "正在连接本地服务…" : "Connecting to local services…",
checking: zh ? "正在检查更新…" : "Checking for updates…",
available: zh ? "新版本已就绪,一次更新 App 与匹配的运行时。" : "Update the App and its matching runtime together.",
up_to_date: zh ? "当前通道暂无更新。" : "No newer update on this channel.",
downloading: zh ? "正在下载并校验签名…" : "Downloading and verifying signature…",
installing_app: zh ? "正在安装 App,请保持窗口打开。" : "Installing the App. Keep this window open.",
installing_runtime: zh ? "正在安装匹配的运行时,请稍候…" : "Installing the matching runtime…",
restart_required: zh ? "重启后将自动完成运行时安装与服务连接。" : "Restart to finish runtime installation and reconnect services.",
ready: zh ? "更新完成,服务已就绪。" : "Update completed; services are ready.",
ready: zh ? "本地服务已就绪。" : "Local services are ready.",
error: updateErrors[state.details?.code ?? ""]?.[zh ? 0 : 1] ?? (repairAvailable
? (zh ? "更新未完成。请重试;启动失败可尝试修复当前版本。" : "Update incomplete. Retry; repair this version if startup fails.")
: (zh ? "更新未完成。请重新检查更新;若仍失败,请重新安装完整 App。" : "Update incomplete. Check for updates again; if it still fails, reinstall the complete App.")),
};
const label = working ? (zh ? "正在更新…" : "Updating…") : state.phase === "available" ? (zh ? "有可用更新" : "Update available") : state.phase === "restart_required" ? (zh ? "重启完成更新" : "Restart to finish") : state.phase === "error" ? (zh ? "更新需重试" : "Retry update") : (zh ? "更新 LoopX" : "Update LoopX");
const label = state.phase === "connecting" ? (zh ? "正在连接…" : "Connecting…") : working ? (zh ? "正在更新…" : "Updating…") : state.phase === "available" ? (zh ? "有可用更新" : "Update available") : state.phase === "restart_required" ? (zh ? "重启完成更新" : "Restart to finish") : state.phase === "error" ? (zh ? "更新需重试" : "Retry update") : (zh ? "更新 LoopX" : "Update LoopX");
return <div className="personal-desktop-update">
<button className="personal-update-trigger" type="button" aria-expanded={open} aria-controls="desktop-update-panel" onClick={(event) => {
panel.current?.style.setProperty("bottom", `${window.innerHeight - event.currentTarget.getBoundingClientRect().top + 8}px`);
Expand Down
35 changes: 33 additions & 2 deletions examples/desktop-update-browser-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ try {
return await nativeActionSettled;
});
await page.addInitScript(() => {
localStorage.setItem("loopx-pw-locale", "zh-CN");
if (!localStorage.getItem("loopx-pw-locale")) localStorage.setItem("loopx-pw-locale", "zh-CN");
window.__TAURI__ = { core: { invoke: (...args) => window.nativeInvoke(...args) } };
});
await page.goto(`${origin}/chat/?statusUrl=/status.json`);
Expand Down Expand Up @@ -243,6 +243,37 @@ try {
assert.equal(calls.length, beforeRead, "reading a failure must not repeat recovery effects");
}

// Ordinary startup and Forget reconnect services without installing updates.
// Readback must not invent an installation effect from those shared phases.
for (const [locale, update, connecting] of [
["zh-CN", "更新 LoopX", "正在连接…"],
["en", "Update LoopX", "Connecting…"],
]) {
const beforeRead = calls.length;
await page.evaluate((value) => localStorage.setItem("loopx-pw-locale", value), locale);
nativeState = { phase: "ready", details: {} };
await page.reload();
await page.getByRole("button", { name: update, exact: true }).click();
const statusText = await page.locator(".personal-update-panel").getByRole("status").innerText();
await page.screenshot({ path: resolve(output, `update-ready-${locale}.png`) });
assert.doesNotMatch(statusText, /更新完成|update completed/i, "ready alone is not evidence of an update");
nativeState = { phase: "connecting", details: {} };
await page.reload();
await page.getByRole("button", { name: connecting, exact: true }).click();
assert.doesNotMatch(await page.locator(".personal-update-panel").getByRole("status").innerText(), /更新后|updated services/i);
assert.equal(calls.length, beforeRead, "reading startup phases must not request an update");
}
await page.evaluate(() => localStorage.setItem("loopx-pw-locale", "zh-CN"));
nativeState = { phase: "ready", details: {} };
await page.setViewportSize({ width: 390, height: 844 });
await page.reload();
await page.getByRole("button", { name: "打开 Goal 导航", exact: true }).click();
await page.getByRole("button", { name: "更新 LoopX", exact: true }).click();
assert.equal(await page.locator(".personal-update-panel").getByRole("status").isVisible(), true);
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth), true);
await page.screenshot({ path: resolve(output, "update-ready-mobile.png") });
await page.setViewportSize({ width: 1280, height: 900 });

// The native owner decides whether Repair can maintain this installation.
// The workspace consumes that projection rather than inferring from a path.
nativeState = { phase: "ready", details: {} };
Expand All @@ -254,7 +285,7 @@ try {
await page.getByText(/当前 App 无法重装运行时/).waitFor();
const beforeForget = calls.length;
await page.getByRole("button", { name: "清除记住的运行时选择", exact: true }).click();
await page.getByText("正在连接更新后的服务…", { exact: true }).waitFor();
await page.getByText("正在连接本地服务…", { exact: true }).waitFor();
assert.deepEqual(calls.slice(beforeForget).map((call) => call.args.action), ["forget_runtime_selection"]);
await nativeActionSettled;
await page.getByRole("button", { name: "清除记住的运行时选择", exact: true }).waitFor({ state: "hidden" });
Expand Down
Loading