diff --git a/.agents/skills/desktop-brand-builder/SKILL.md b/.agents/skills/desktop-brand-builder/SKILL.md index d670075ad..c22d02305 100644 --- a/.agents/skills/desktop-brand-builder/SKILL.md +++ b/.agents/skills/desktop-brand-builder/SKILL.md @@ -65,22 +65,22 @@ Use explicit user-provided override values as-is after basic validation. ## Build Workflow Use an isolated build directory under the current working directory so user -changes in the current worktree are not mutated. Default to the qwen-code desktop branch; do not clone from -`craft-agents-oss`, OpenWork, or another local checkout unless the user -explicitly asks for that source: +changes in the current worktree are not mutated. Default to the qwen-code main +branch; do not clone from `craft-agents-oss`, OpenWork, or another local +checkout unless the user explicitly asks for that source: ```bash BUILD_ROOT="$PWD/brand-builds/-" mkdir -p "$BUILD_ROOT" -git clone --branch dragon/feat-unstable-desktop-app --single-branch \ +git clone --branch main --single-branch \ https://github.com/QwenLM/qwen-code.git \ "$BUILD_ROOT/qwen-code" cd "$BUILD_ROOT/qwen-code" -git checkout -B dragon/brand- origin/dragon/feat-unstable-desktop-app +git checkout -B brand- origin/main ``` If the branch fetch or checkout fails, stop and report the failure. Do not -continue as if `dragon/brand-` was created. +continue as if `brand-` was created. Create a temporary `brand.json` in the build directory: diff --git a/apps/electron/scripts/build-dmg.sh b/apps/electron/scripts/build-dmg.sh index 9a91a0ce4..8127fd15f 100755 --- a/apps/electron/scripts/build-dmg.sh +++ b/apps/electron/scripts/build-dmg.sh @@ -150,17 +150,15 @@ if [ -n "$APPLE_SIGNING_IDENTITY" ]; then export CSC_NAME="$CSC_NAME_CLEAN" fi -# Add notarization if all credentials are available +# Add notarization if all credentials are available. +# electron-builder (>=24) auto-notarizes via notarytool whenever APPLE_ID, +# APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env. +# No `notarize:` config block or NOTARIZE flag is required (or read). if [ -n "$APPLE_ID" ] && [ -n "$APPLE_TEAM_ID" ] && [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ]; then echo "Notarization enabled" export APPLE_ID="$APPLE_ID" export APPLE_TEAM_ID="$APPLE_TEAM_ID" export APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD" - - # Enable notarization in electron-builder by setting env vars - # The electron-builder.yml has notarize section commented out, - # but we can enable it via environment - export NOTARIZE=true fi # Run electron-builder diff --git a/apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx b/apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx index f9dfb43c5..7f535b4b8 100644 --- a/apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx +++ b/apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx @@ -11,6 +11,7 @@ import { ChevronDown, AlertCircle, CornerDownRight, + GitBranch, X, } from 'lucide-react'; import { Icon_Home, Icon_Folder } from '@craft-agent/ui'; @@ -2753,15 +2754,22 @@ function WorkingDirectoryBadge({ // Fetch git branch when working directory changes React.useEffect(() => { + let cancelled = false; + setGitBranch(null); + if (workingDirectory) { window.electronAPI ?.getGitBranch?.(workingDirectory) .then((branch: string | null) => { - setGitBranch(branch); + if (!cancelled) { + setGitBranch(branch); + } }); - } else { - setGitBranch(null); } + + return () => { + cancelled = true; + }; }, [workingDirectory]); // Reset filter, refresh history, and focus input when popover opens @@ -2833,6 +2841,7 @@ function WorkingDirectoryBadge({ const folderName = hasFolder ? getPathBasename(workingDirectory) || 'Folder' : 'Work in Folder'; + const visibleGitBranch = hasFolder ? gitBranch : null; // Show reset option when a folder is selected and it differs from session folder const showReset = @@ -2854,6 +2863,21 @@ function WorkingDirectoryBadge({ } label={folderName} + ariaLabel={ + visibleGitBranch + ? `${folderName}, ${t('chat.onBranch', { + branch: visibleGitBranch, + })}` + : folderName + } + trailingContent={ + visibleGitBranch ? ( + + + {visibleGitBranch} + + ) : undefined + } isExpanded={isEmptySession} hasSelection={hasFolder} showChevron={true} diff --git a/apps/electron/src/renderer/components/app-shell/input/FreeFormInputContextBadge.tsx b/apps/electron/src/renderer/components/app-shell/input/FreeFormInputContextBadge.tsx index f13a1271a..87e3b5e65 100644 --- a/apps/electron/src/renderer/components/app-shell/input/FreeFormInputContextBadge.tsx +++ b/apps/electron/src/renderer/components/app-shell/input/FreeFormInputContextBadge.tsx @@ -9,6 +9,10 @@ export interface FreeFormInputContextBadgeProps { icon: React.ReactNode /** Label text - shown in expanded state or collapsed with selection */ label: string + /** Accessible label for the full badge when visible metadata is appended */ + ariaLabel?: string + /** Optional trailing metadata shown after the label */ + trailingContent?: React.ReactNode /** Whether to show expanded state (icon + label + chevron) vs collapsed */ isExpanded?: boolean /** Whether there's an active selection (affects collapsed state styling and shows label) */ @@ -45,6 +49,8 @@ export const FreeFormInputContextBadge = React.forwardRef} type="button" - aria-label={label} + aria-label={ariaLabel ?? label} onClick={onClick} disabled={disabled} data-tutorial={dataTutorial} @@ -107,6 +113,8 @@ export const FreeFormInputContextBadge = React.forwardRef diff --git a/docs/plans/2026-06-08-desktop-auto-update-design.md b/docs/plans/2026-06-08-desktop-auto-update-design.md index 0cd193a55..192d1c1bf 100644 --- a/docs/plans/2026-06-08-desktop-auto-update-design.md +++ b/docs/plans/2026-06-08-desktop-auto-update-design.md @@ -23,13 +23,13 @@ Before this change, the desktop app already had most of the runtime surface: Update source configuration belongs in `packages/shared/src/branding.ts` with the rest of desktop brand metadata. Each brand owns its release location: - `openwork` uses `modelstudioai/openwork`. -- `qwen-code` uses `QwenLM/qwen-code`. +- `qwen-code` uses a fixed `QwenLM/qwen-code` `desktop-latest` release download URL so desktop updates do not depend on the repository-wide GitHub latest release. -The brand config exposes a GitHub update source with `provider`, `owner`, `repo`, and `releasePageUrl`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source for logs and fallback release-page actions. +The brand config exposes an update source plus `releasePageUrl`. GitHub sources use `provider`, `owner`, and `repo`; generic sources use `provider` and `url`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source to decide whether packaged builds can check for updates. ## Release Flow -The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself. +The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself. Qwen Code publishes versioned `desktop-v*` releases for history and also clobbers the fixed `desktop-latest` release used by the generic update feed. Expected assets include platform installers and feed files: diff --git a/packages/shared/src/branding.ts b/packages/shared/src/branding.ts index 3f085608f..5a887163c 100644 --- a/packages/shared/src/branding.ts +++ b/packages/shared/src/branding.ts @@ -10,6 +10,21 @@ // Brand config type // --------------------------------------------------------------------------- +type GitHubUpdateSource = { + provider: 'github'; + owner: string; + repo: string; + releasePageUrl: string; +}; + +type GenericUpdateSource = { + provider: 'generic'; + url: string; + releasePageUrl: string; +}; + +type UpdateSource = GitHubUpdateSource | GenericUpdateSource; + export interface BrandConfig { /** Internal identifier */ id: string; @@ -30,12 +45,7 @@ export interface BrandConfig { /** Session viewer base URL */ viewerUrl: string; /** Stable desktop auto-update source for packaged app builds. */ - updates?: { - provider: 'github'; - owner: string; - repo: string; - releasePageUrl: string; - }; + updates?: UpdateSource; /** Brand-owned external links shown in the Help menu */ helpMenuLinks: Array<{ labelKey: string; url: string; icon: string }>; /** Brand-specific Electron resource paths, relative to apps/electron/ */ @@ -80,9 +90,8 @@ const QWEN_CODE_BRAND: BrandConfig = { selfReferName: 'Qwen Code', viewerUrl: 'https://agents.craft.do', updates: { - provider: 'github', - owner: 'QwenLM', - repo: 'qwen-code', + provider: 'generic', + url: 'https://github.com/QwenLM/qwen-code/releases/download/desktop-latest', releasePageUrl: 'https://github.com/QwenLM/qwen-code/releases', }, helpMenuLinks: [ diff --git a/scripts/build/darwin.ts b/scripts/build/darwin.ts index dce184c3f..f5f86635f 100644 --- a/scripts/build/darwin.ts +++ b/scripts/build/darwin.ts @@ -32,14 +32,16 @@ export async function packageDarwin(config: BuildConfig): Promise { process.env.CSC_NAME = cscName; } - // Add notarization if all credentials are available + // Add notarization if all credentials are available. + // electron-builder auto-notarizes via notarytool when APPLE_ID, + // APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env; + // no `notarize:` config block or NOTARIZE flag is required (or read). if ( process.env.APPLE_ID && process.env.APPLE_TEAM_ID && process.env.APPLE_APP_SPECIFIC_PASSWORD ) { console.log(' Notarization enabled'); - process.env.NOTARIZE = 'true'; } // Run electron-builder diff --git a/scripts/electron-builder-config.ts b/scripts/electron-builder-config.ts index 7fc7edaaf..f8be4b091 100644 --- a/scripts/electron-builder-config.ts +++ b/scripts/electron-builder-config.ts @@ -53,11 +53,18 @@ linux.icon = BRAND.assets.linuxIcon; linux.artifactName = artifactName; if (BRAND.updates) { - config.publish = { - provider: BRAND.updates.provider, - owner: BRAND.updates.owner, - repo: BRAND.updates.repo, - }; + if (BRAND.updates.provider === 'github') { + config.publish = { + provider: BRAND.updates.provider, + owner: BRAND.updates.owner, + repo: BRAND.updates.repo, + }; + } else { + config.publish = { + provider: BRAND.updates.provider, + url: BRAND.updates.url, + }; + } } else { delete config.publish; } diff --git a/scripts/vendor-qwen-code.ts b/scripts/vendor-qwen-code.ts index 020739290..02d0f3184 100644 --- a/scripts/vendor-qwen-code.ts +++ b/scripts/vendor-qwen-code.ts @@ -102,7 +102,7 @@ async function vendorLocalCheckout(repoRoot: string): Promise { console.log(`Building Qwen Code CLI from ${repoRoot}...`); const npm = npmCommand(); - await run([npm, 'run', 'build'], repoRoot); + await run([npm, 'run', 'build', '--', '--cli-only'], repoRoot); await run([npm, 'run', 'bundle'], repoRoot); await run([npm, 'run', 'prepare:package'], repoRoot);