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
18 changes: 15 additions & 3 deletions packages/desktop-shell/bootstrap/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const logs = document.querySelector('#logs');
const update = document.querySelector('#update');
const version = document.querySelector('#version');

let currentWorkspace = '';
let snapshotOverrideStatus;
let updateVersion;

function setWorkspace(path) {
Expand All @@ -21,6 +23,7 @@ function setWorkspace(path) {
}

function setStatus(kind, heading, message, failure = '') {
document.body.dataset.state = kind;
title.textContent = heading;
detail.textContent = message;
pulse.className = `pulse ${kind === 'starting' ? '' : kind}`;
Expand All @@ -29,18 +32,20 @@ function setStatus(kind, heading, message, failure = '') {
retry.hidden = kind !== 'error';
choose.hidden = kind === 'starting';
choose.disabled = kind === 'starting';
setWorkspace(kind === 'starting' ? '' : currentWorkspace);
}

async function chooseWorkspace() {
if (!invoke) return;
snapshotOverrideStatus = 'starting';
setStatus(
'starting',
'Opening workspace',
'Starting the bundled Qwen Code runtime…',
);
try {
const path = await invoke('choose_workspace');
if (path) setWorkspace(path);
if (path) currentWorkspace = path;
else
setStatus('idle', 'Choose another workspace', 'No folder was selected.');
} catch (failure) {
Expand All @@ -55,6 +60,7 @@ async function chooseWorkspace() {

async function retryRuntime() {
if (!invoke) return;
snapshotOverrideStatus = 'starting';
setStatus(
'starting',
'Restarting Qwen Code',
Expand Down Expand Up @@ -123,14 +129,16 @@ async function initialize() {

await Promise.all([
listen('runtime-starting', ({ payload }) => {
setWorkspace(payload);
snapshotOverrideStatus = 'starting';
currentWorkspace = String(payload || '');
setStatus(
'starting',
'Starting Qwen Code',
'Launching the bundled runtime and checking its health…',
);
}),
listen('runtime-failed', ({ payload }) => {
snapshotOverrideStatus = 'failed';
setStatus(
'error',
'Qwen Code could not start',
Expand All @@ -147,7 +155,11 @@ async function initialize() {

const state = await invoke('bootstrap_state');
version.textContent = `Desktop ${state.desktopVersion}`;
setWorkspace(state.workspace);
currentWorkspace ||= String(state.workspace || '');
if (snapshotOverrideStatus) {
if (snapshotOverrideStatus === 'failed') setWorkspace(currentWorkspace);
return;
}
if (state.status === 'starting') {
setStatus(
'starting',
Expand Down
83 changes: 71 additions & 12 deletions packages/desktop-shell/bootstrap/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,75 @@
gap: 18px;
}
.mark {
display: grid;
place-items: center;
width: 58px;
height: 58px;
border-radius: 18px;
background: linear-gradient(145deg, #7f6dff, #4d34df);
box-shadow: 0 14px 34px rgba(88, 63, 226, 0.38);
font:
700 26px/1 ui-monospace,
monospace;
object-fit: contain;
filter: drop-shadow(0 14px 24px rgba(88, 63, 226, 0.38));
}
body[data-state='starting'] {
background: #070b12;
}
body[data-state='starting']::before,
body[data-state='starting'] .brand-copy,
body[data-state='starting'] .actions,
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
body[data-state='starting'] footer {
display: none;
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
}
body[data-state='starting'] .shell {
width: auto;
border: 0;
padding: 0;
background: none;
box-shadow: none;
backdrop-filter: none;
}
body[data-state='starting'] .mark {
animation: mark-pulse 1.6s ease-in-out infinite;
}
body[data-state='starting'] .status {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
border: 0;
clip: rect(0, 0, 0, 0);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
clip-path: inset(50%);
white-space: nowrap;
}
@keyframes mark-pulse {
50% {
opacity: 0.55;
transform: scale(0.94);
}
}
@media (prefers-reduced-motion: reduce) {
body[data-state='starting'] .mark {
animation: none;
}
body[data-state='starting'] .status {
position: static;
width: auto;
height: auto;
margin: 18px 0 0;
overflow: visible;
clip: auto;
clip-path: none;
white-space: normal;
grid-template-columns: 1fr;
color: #8d98aa;
background: none;
text-align: center;
}
body[data-state='starting'] .status > :first-child,
body[data-state='starting'] .status > div > :not(#title) {
display: none;
}
body[data-state='starting'] #title {
font-size: 13px;
font-weight: 500;
}
}
h1 {
margin: 0;
Expand All @@ -105,7 +164,7 @@
}
.status {
display: grid;
grid-template-columns: 22px 1fr;
grid-template-columns: 22px minmax(0, 1fr);
gap: 14px;
margin-top: 40px;
padding: 22px 24px;
Expand Down Expand Up @@ -232,12 +291,12 @@
}
</style>
</head>
<body>
<body data-state="starting">
<main>
<section class="shell" aria-live="polite">
<div class="brand">
<div class="mark">Q</div>
<div>
<img class="mark" src="qwen-code-logo.svg" alt="Qwen Code" />
<div class="brand-copy">
<h1>Qwen Code</h1>
<p class="eyebrow">Desktop workspace</p>
</div>
Expand Down
6 changes: 6 additions & 0 deletions packages/desktop-shell/bootstrap/qwen-code-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
134 changes: 134 additions & 0 deletions packages/desktop-shell/scripts/test-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import vm from 'node:vm';
import { fileURLToPath } from 'node:url';
import { resolveLogRoot, sliceNewLog } from './resolve-log-root.js';

Expand Down Expand Up @@ -39,6 +40,7 @@ const root = fs.mkdtempSync(
);
try {
testBootstrapBridgeConfiguration();
await testBootstrapWorkspaceVisibility();
testLegacyApplicationIdentity();
testElectronBridgeWorkflow();
testDesktopReleaseSigningWorkflow();
Expand All @@ -53,6 +55,138 @@ try {
fs.rmSync(root, { recursive: true, force: true });
}

async function testBootstrapWorkspaceVisibility() {
const bootstrapHtml = fs.readFileSync(
path.join(packageDir, 'bootstrap', 'index.html'),
'utf8',
);
assert.match(bootstrapHtml, /class="mark" src="qwen-code-logo\.svg"/);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
assert.ok(
fs.existsSync(path.join(packageDir, 'bootstrap', 'qwen-code-logo.svg')),
'The bootstrap splash mark must ship with the frontendDist directory.',
);
assert.doesNotMatch(bootstrapHtml, /class="mark">Q</);
const primary = await createBootstrapHarness();
const { body, commands, element, listeners, resolveBootstrapState } = primary;

listeners['runtime-starting']({
payload: '/Users/example/Projects/qwen-code',
});
assert.equal(body.dataset.state, 'starting');
assert.equal(element('#workspace').hidden, true);

listeners['runtime-failed']({ payload: 'runtime failed' });
assert.equal(body.dataset.state, 'error');
assert.equal(element('#workspace').hidden, false);
assert.equal(
element('#workspace').textContent,
'/Users/example/Projects/qwen-code',
);
await element('#logs').listeners.click();
assert.equal(element('#workspace').hidden, false);

element('#retry').listeners.click();
assert.equal(commands.at(-1), 'restart_runtime');
assert.equal(body.dataset.state, 'starting');
assert.equal(element('#workspace').hidden, true);

resolveBootstrapState({
desktopVersion: '0.2.0',
status: 'starting',
workspace: '/Users/example/Documents',
});
await new Promise((resolve) => setImmediate(resolve));
assert.equal(element('#title').textContent, 'Restarting Qwen Code');
assert.equal(
element('#workspace').hidden,
true,
'A stale bootstrap snapshot must not overwrite a newer recovery action.',
);

const failed = await createBootstrapHarness();
failed.listeners['runtime-failed']({ payload: 'runtime failed' });
failed.resolveBootstrapState({
desktopVersion: '0.2.0',
status: 'idle',
workspace: '/Users/example/Documents/Qwen',
error: 'runtime failed',
});
await new Promise((resolve) => setImmediate(resolve));
assert.equal(failed.element('#workspace').hidden, false);
assert.equal(
failed.element('#workspace').textContent,
'/Users/example/Documents/Qwen',
);

const cancelled = await createBootstrapHarness();
cancelled.listeners['runtime-starting']({
payload: '/Users/example/Documents/Qwen',
});
cancelled.listeners['runtime-failed']({ payload: 'runtime failed' });
await cancelled.element('#choose').listeners.click();
assert.equal(cancelled.body.dataset.state, 'idle');
assert.equal(cancelled.element('#workspace').hidden, false);
assert.equal(
cancelled.element('#workspace').textContent,
'/Users/example/Documents/Qwen',
);
}

async function createBootstrapHarness() {
const elements = {};
const element = (selector) => {
elements[selector] ??= {
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
addEventListener(event, listener) {
this.listeners ??= {};
this.listeners[event] = listener;
},
style: {},
};
return elements[selector];
};
const listeners = {};
const commands = [];
const body = { dataset: {} };
let resolveBootstrapState;
const tauri = {
core: {
invoke: async (command) => {
commands.push(command);
if (command === 'bootstrap_state') {
return new Promise((resolve) => {
resolveBootstrapState = resolve;
});
}
if (command === 'open_logs') throw new Error('no file handler');
if (command === 'choose_workspace') return null;
if (command === 'restart_runtime') return new Promise(() => {});
throw new Error(`Unexpected desktop command: ${command}`);
},
},
event: {
listen: async (event, listener) => {
listeners[event] = listener;
},
},
};
vm.runInNewContext(
fs.readFileSync(path.join(packageDir, 'bootstrap', 'bootstrap.js'), 'utf8'),
{
document: { body, querySelector: element },
window: { __TAURI__: tauri },
},
{ timeout: 5000 },
);
await new Promise((resolve) => setImmediate(resolve));
return {
body,
commands,
element,
listeners,
resolveBootstrapState: (state) => resolveBootstrapState(state),
};
}

function testLegacyApplicationIdentity() {
const config = JSON.parse(
fs.readFileSync(
Expand Down
Loading
Loading