diff --git a/.github/workflows/release-app.yml b/.github/workflows/release-app.yml index 2f0d626a..10ddf2cd 100644 --- a/.github/workflows/release-app.yml +++ b/.github/workflows/release-app.yml @@ -26,19 +26,19 @@ jobs: os: "macos" runtime: "wry" targets: "x86_64-apple-darwin" - - platform: "ubuntu-24.04" + - platform: "ubuntu-22.04" args: '--config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}''' yaak_arch: "x64" os: "ubuntu" runtime: "wry" targets: "" - - platform: "ubuntu-24.04-arm" + - platform: "ubuntu-22.04-arm" args: '--config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}''' yaak_arch: "arm64" os: "ubuntu" runtime: "wry" targets: "" - - platform: "ubuntu-24.04" + - platform: "ubuntu-22.04" args: >- --bundles deb --config ./tauri.release.conf.json @@ -47,7 +47,7 @@ jobs: os: "ubuntu" runtime: "cef" targets: "" - - platform: "ubuntu-24.04-arm" + - platform: "ubuntu-22.04-arm" args: >- --bundles deb --config ./tauri.release.conf.json @@ -102,7 +102,13 @@ jobs: if: matrix.os == 'ubuntu' run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libnss3 patchelf xdg-utils + sudo apt-get install -y cmake ninja-build libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libnss3 patchelf xdg-utils clang-15 llvm-15 + # crates/yaak-web compiles SQLite to wasm via sqlite-wasm-rs, whose C shim + # uses C23 [[noreturn]]. Ubuntu 22.04's default clang-14 rejects it; clang-15 + # from the same repos accepts it. Only the wasm build uses this compiler, so + # the shipped binary keeps 22.04's glibc floor. + echo "CC_wasm32_unknown_unknown=/usr/bin/clang-15" >> "$GITHUB_ENV" + echo "AR_wasm32_unknown_unknown=/usr/bin/llvm-ar-15" >> "$GITHUB_ENV" - name: Install Protoc for plugin-runtime uses: arduino/setup-protoc@v3 diff --git a/apps/yaak-client/hooks/useHotKey.ts b/apps/yaak-client/hooks/useHotKey.ts index 39788b73..f0efe8d6 100644 --- a/apps/yaak-client/hooks/useHotKey.ts +++ b/apps/yaak-client/hooks/useHotKey.ts @@ -333,7 +333,9 @@ export function formatHotkeyString(trigger: string): string[] { } else if (p === "Alt") { labelParts.push("⌥"); } else if (p === "Enter") { - labelParts.push("↩"); + // U+21A9 has an emoji presentation, which Chromium's font fallback picks + // (a blue glyph among monochrome ones). U+FE0E forces the text form. + labelParts.push("↩︎"); } else if (p === "Tab") { labelParts.push("⇥"); } else if (p === "Backspace") { diff --git a/packages/platform/src/tauri/index.ts b/packages/platform/src/tauri/index.ts index 9d8c1005..271ed37f 100644 --- a/packages/platform/src/tauri/index.ts +++ b/packages/platform/src/tauri/index.ts @@ -58,6 +58,7 @@ const ALL_CAPABILITIES: PlatformCapabilities = { localFiles: true, timeline: true, multiWindow: true, + windowChrome: true, plugins: true, encryption: true, updater: true, diff --git a/packages/platform/src/types.ts b/packages/platform/src/types.ts index b34fd7a8..de077348 100644 --- a/packages/platform/src/types.ts +++ b/packages/platform/src/types.ts @@ -255,6 +255,13 @@ export interface PlatformCapabilities { timeline: boolean; /** More than one window or tab on the same data. */ multiWindow: boolean; + /** + * The page is the window's titlebar: it draws the drag region and window + * controls, and leaves room for macOS traffic lights. False when something + * else owns the frame around the page (a browser tab), so none of that + * chrome should be reserved or drawn. + */ + windowChrome: boolean; /** The plugin runtime. */ plugins: boolean; /** Workspace encryption backed by a key the host keeps. */ diff --git a/packages/platform/src/web/README.md b/packages/platform/src/web/README.md index b8c2db81..c80cd97d 100644 --- a/packages/platform/src/web/README.md +++ b/packages/platform/src/web/README.md @@ -129,13 +129,16 @@ Reported honestly, so callers gate on the question rather than on the host: | True | False | | --- | --- | -| `cookieJar` (the jar stores and edits here; only filling it needs the sender) | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `timeline`, `multiWindow`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` | +| `cookieJar` (the jar stores and edits here; only filling it needs the sender) | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `timeline`, `multiWindow`, `windowChrome`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` | `multiWindow: false` means the host cannot open a *second window* on demand — what `cmd_new_child_window` does for Settings and workspace switching. It is not a claim that nothing else is looking: other tabs may well be open on the same worker, and it pushes every write to all of them regardless. +`windowChrome: false` means the browser owns the frame around the page, so the +header draws no window controls and reserves no room for macOS traffic lights. + ## Multiple tabs Each tab mints a label at load (`tab_xxxxxxxx`) and sends it with every command; diff --git a/packages/platform/src/web/index.ts b/packages/platform/src/web/index.ts index 006d2364..530ae4e4 100644 --- a/packages/platform/src/web/index.ts +++ b/packages/platform/src/web/index.ts @@ -49,6 +49,9 @@ function capabilitiesFor(): PlatformCapabilities { // else is looking: other tabs may well be open on the same worker, and it // pushes every write to all of them regardless of this flag. multiWindow: false, + // The browser draws the frame around the page. There are no traffic lights + // to leave room for and no window controls to draw. + windowChrome: false, plugins: false, encryption: false, updater: false, diff --git a/packages/ui/src/components/HeaderSize.tsx b/packages/ui/src/components/HeaderSize.tsx index 79c13a72..8056696d 100644 --- a/packages/ui/src/components/HeaderSize.tsx +++ b/packages/ui/src/components/HeaderSize.tsx @@ -1,3 +1,4 @@ +import { useCapability } from "@yaakapp-internal/platform"; import classNames from "classnames"; import type { CSSProperties, HTMLAttributes, ReactNode } from "react"; import { useMemo } from "react"; @@ -31,6 +32,10 @@ export function HeaderSize({ interfaceScale, }: HeaderSizeProps) { const isFullscreen = useIsFullscreen(); + // The header only doubles as the titlebar when the host hands the page the + // window frame (Tauri) and the user hasn't opted for the native one. In a + // browser tab the frame is the browser's: no controls, no traffic lights. + const drawsWindowChrome = useCapability("windowChrome") && !useNativeTitlebar; const finalStyle = useMemo(() => { const s = { ...style }; @@ -38,8 +43,8 @@ export function HeaderSize({ if (size === "md") s.minHeight = HEADER_SIZE_MD; if (size === "lg") s.minHeight = HEADER_SIZE_LG; - if (useNativeTitlebar) { - // No style updates when using native titlebar + if (!drawsWindowChrome) { + // No style updates when something else draws the titlebar } else if (osType === "macos") { if (!isFullscreen) { // Add large padding for window controls @@ -57,7 +62,7 @@ export function HeaderSize({ interfaceScale, size, style, - useNativeTitlebar, + drawsWindowChrome, osType, ]); @@ -82,7 +87,7 @@ export function HeaderSize({ > {children} - {!hideControls && !useNativeTitlebar && ( + {!hideControls && drawsWindowChrome && (